Skip to content

refactor(router): use lazy route loading and add bundle splitting#65

Merged
sam821203 merged 1 commit intodevelopfrom
refactor/router-lazy-load-and-bundle-split
Mar 14, 2026
Merged

refactor(router): use lazy route loading and add bundle splitting#65
sam821203 merged 1 commit intodevelopfrom
refactor/router-lazy-load-and-bundle-split

Conversation

@sam821203
Copy link
Copy Markdown
Owner

@sam821203 sam821203 commented Mar 14, 2026

Summary

  • What problem does this PR solve? The app was loading all route components up front, increasing initial bundle size and time-to-interactive. There was no explicit bundle splitting for vendor code.
  • What approach did you take? Switched to TanStack Router lazy routes (dynamic imports + createLazyRoute) so each page is loaded on demand, and added Vite manualChunks plus the bundle visualizer to split vendors (React, Redux, Clerk, TanStack) and inspect output.

Changes

  • Use lazy route loading in router.tsx: replace static page imports with .lazy() + dynamic import() and createLazyRoute for all route components
  • Add Vite bundle splitting: configure rollupOptions.output.manualChunks to separate react-vendor, redux, clerk, and tanstack from the main bundle
  • Add rollup-plugin-visualizer in vite.config.ts (stats.html, gzip size) for bundle analysis
  • Update apps/web/package.json and eslint.config.js as needed for the refactor

Screenshots / Recording (if UI changes)

image

Scope

Select one (aligns with commitlint.config.cjs scopes):

  • ui
  • layout
  • page
  • component
  • hook
  • api
  • state
  • router
  • auth
  • permission
  • i18n
  • theme
  • devops

Related

Branch: refactor/router-lazy-load-and-bundle-split

Summary by CodeRabbit

  • Performance Improvements
    • Implemented lazy-loading for route components, reducing initial page load time by deferring non-critical route code.
    • Enhanced code splitting to isolate vendor dependencies into separate bundles, improving caching and load performance.
    • Added build visualization tool to monitor bundle size and optimize deployment efficiency.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

The PR implements lazy-loaded route components using createLazyRoute from @tanstack/react-router, reducing initial bundle size. Bundle splitting is configured in Vite via rollup-plugin-visualizer and manualChunks to create separate chunks for Redux, Clerk, TanStack, and React vendors. ESLint configuration is updated to include additional TypeScript projects.

Changes

Cohort / File(s) Summary
Router Lazy Loading
apps/web/src/router.tsx
Replaced 20+ static route component imports with lazy-loaded variants using createLazyRoute and dynamic imports. All dashboard, ecommerce, calendar, profile, settings, tables, chart, UI, forms, auth, finance, and audit routes now load on-demand.
Build Configuration & Tooling
apps/web/vite.config.ts, apps/web/package.json, eslint.config.js
Added rollup-plugin-visualizer dependency with Vite configuration for bundle splitting via manualChunks function that creates separate chunks for Redux, Clerk, TanStack, and React vendors. Extended ESLint TypeScript project configuration to include tsconfig.node.json.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Routes now hop lazily through code,
Each chunk splits along the road,
Bundles shrink as users roam,
Faster loads bring pages home! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main changes: lazy route loading and bundle splitting configuration.
Description check ✅ Passed The description covers summary, approach, changes, scope selection, and related issue reference, matching the template requirements.
Linked Issues check ✅ Passed All acceptance criteria from #64 are satisfied: lazy routes via createLazyRoute [#64], manual chunks for bundle splitting [#64], ESLint config updated [#64], and behavior preserved across all routes [#64].
Out of Scope Changes check ✅ Passed All changes directly support the objective: router lazy loading, bundle splitting, visualizer, and configuration updates. No unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/router-lazy-load-and-bundle-split
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/vite.config.ts (1)

19-37: Give manualChunks an explicit return type.

This callback is now the bundle-splitting policy for the app. Annotating it as string | undefined makes the fallback-to-Rollup branch explicit and keeps this TS file aligned with the repo type-safety rule.

♻️ Small typed cleanup
-        manualChunks(id) {
+        manualChunks(id): string | undefined {
As per coding guidelines "Explicit Return Types: All functions, especially exported ones, must have explicit return type definitions to prevent unintended type inference."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/vite.config.ts` around lines 19 - 37, The manualChunks callback
currently relies on inferred types; give it an explicit return type of string |
undefined by annotating the function signature (manualChunks(id): string |
undefined) so the bundling policy is typed and makes the Rollup fallback
explicit; update the manualChunks declaration where it's defined to include this
return type and ensure its branches continue to return string values or
undefined.
apps/web/src/router.tsx (1)

218-218: Consider turning on intent preloading at the router level.

defaultPreload still defaults to false, while 'intent' preloads route dependencies on hover/touchstart for TanStack <Link> components. If the sidebar/navigation already uses those links, setting it here is a cheap way to hide most first-navigation chunk latency now that every page is lazy. (tanstack.com)

⚡ Small router tweak
-export const router = createRouter({ routeTree });
+export const router = createRouter({
+  routeTree,
+  defaultPreload: 'intent',
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/router.tsx` at line 218, The router is created with
createRouter({ routeTree }) but defaultPreload remains false; enable intent
preloading by passing defaultPreload: 'intent' into the createRouter options so
the exported router uses intent preloading for TanStack <Link> (e.g., change the
createRouter call that defines router to include defaultPreload: 'intent'
alongside routeTree to preload on hover/touchstart).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/package.json`:
- Line 45: The new devDependency "rollup-plugin-visualizer"@^6.0.11 requires
Node ≥22 which conflicts with the repo's current .nvmrc pinned Node 20.19.2; fix
by either (A) updating .nvmrc to Node 22.x (and, if desired, updating CI to use
Node 22), or (B) downgrade the dependency in apps/web/package.json to a 5.x
release compatible with Node 20 (replace "rollup-plugin-visualizer": "^6.0.11"
with a 5.x semver), or (C) explicitly declare the higher requirement by adding
an "engines": { "node": ">=22" } field to apps/web/package.json so the
requirement is enforced—pick one approach and apply the corresponding change
consistently (update .nvmrc/CI for A, modify package.json dependency for B, or
add engines.node for C).

---

Nitpick comments:
In `@apps/web/src/router.tsx`:
- Line 218: The router is created with createRouter({ routeTree }) but
defaultPreload remains false; enable intent preloading by passing
defaultPreload: 'intent' into the createRouter options so the exported router
uses intent preloading for TanStack <Link> (e.g., change the createRouter call
that defines router to include defaultPreload: 'intent' alongside routeTree to
preload on hover/touchstart).

In `@apps/web/vite.config.ts`:
- Around line 19-37: The manualChunks callback currently relies on inferred
types; give it an explicit return type of string | undefined by annotating the
function signature (manualChunks(id): string | undefined) so the bundling policy
is typed and makes the Rollup fallback explicit; update the manualChunks
declaration where it's defined to include this return type and ensure its
branches continue to return string values or undefined.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5c8c5052-3903-434a-9795-3225426d76d0

📥 Commits

Reviewing files that changed from the base of the PR and between c5a2c8e and 8d2c013.

📒 Files selected for processing (4)
  • apps/web/package.json
  • apps/web/src/router.tsx
  • apps/web/vite.config.ts
  • eslint.config.js

Comment thread apps/web/package.json
"globals": "^16.5.0",
"jsdom": "^26.0.0",
"postcss": "^8.5.1",
"rollup-plugin-visualizer": "^6.0.11",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package.json engines.node =="
while IFS= read -r path; do
  python - "$path" <<'PY'
import json, sys
path = sys.argv[1]
with open(path) as f:
    data = json.load(f)
print(f"{path}: {data.get('name')} -> {data.get('engines', {}).get('node', '<no engines.node>')}")
PY
done < <(fd '^package\.json$' -HI)

echo
echo "== version manager files =="
fd '(^\.nvmrc$|^\.node-version$|^\.tool-versions$)' -HI -x sh -c 'echo "--- $1"; cat "$1"; echo' sh {}

echo
echo "== CI node-version declarations =="
if [ -d .github/workflows ]; then
  rg -nC2 'setup-node|node-version' .github/workflows
fi

Repository: sam821203/SalesOps

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Find all package.json files and extract engines.node
echo "== package.json engines.node =="
fd '^package\.json$' -HI | while read -r path; do
  python3 - "$path" <<'PY'
import json, sys
try:
    with open(sys.argv[1]) as f:
        data = json.load(f)
    engines_node = data.get('engines', {}).get('node', '<not specified>')
    print(f"{sys.argv[1]}: {engines_node}")
except Exception as e:
    print(f"Error reading {sys.argv[1]}: {e}")
PY
done

echo
echo "== Node version manager files =="
fd '(^\.nvmrc$|^\.node-version$|^\.tool-versions$)' -HI

echo
echo "== .nvmrc contents (if exists) =="
if [ -f .nvmrc ]; then
  echo "Contents of .nvmrc:"; cat .nvmrc
fi

echo
echo "== Vite version in package.json =="
fd '^package\.json$' -HI | head -5 | while read -r path; do
  grep -i 'vite' "$path" | head -3 || echo "No vite found in $path"
done

echo
echo "== GitHub Actions Node version setup =="
if [ -d .github/workflows ]; then
  rg 'node-version|setup-node' .github/workflows -A 2 -B 2
fi

Repository: sam821203/SalesOps

Length of output: 17263


🏁 Script executed:

#!/bin/bash

echo "== Vite version in apps/web/package.json =="
grep '"vite"' apps/web/package.json

echo
echo "== rollup-plugin-visualizer version in apps/web/package.json =="
grep 'rollup-plugin-visualizer' apps/web/package.json

echo
echo "== Node version engines check in apps/web/package.json =="
cat apps/web/package.json | head -30

Repository: sam821203/SalesOps

Length of output: 1135


Bump Node to 22.x or downgrade the visualizer before merging.

The repository is pinned to Node 20.19.2 (via .nvmrc), but rollup-plugin-visualizer 6.x requires Node ≥ 22. Vite 7.2.4 supports Node 20.19+, but this new devDependency raises the floor above the project's current pinned version. Either update .nvmrc to Node 22+, switch to rollup-plugin-visualizer 5.x (compatible with Node 20), or set engines.node in apps/web/package.json to enforce the higher requirement.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/package.json` at line 45, The new devDependency
"rollup-plugin-visualizer"@^6.0.11 requires Node ≥22 which conflicts with the
repo's current .nvmrc pinned Node 20.19.2; fix by either (A) updating .nvmrc to
Node 22.x (and, if desired, updating CI to use Node 22), or (B) downgrade the
dependency in apps/web/package.json to a 5.x release compatible with Node 20
(replace "rollup-plugin-visualizer": "^6.0.11" with a 5.x semver), or (C)
explicitly declare the higher requirement by adding an "engines": { "node":
">=22" } field to apps/web/package.json so the requirement is enforced—pick one
approach and apply the corresponding change consistently (update .nvmrc/CI for
A, modify package.json dependency for B, or add engines.node for C).

@sam821203 sam821203 merged commit 6003445 into develop Mar 14, 2026
7 checks passed
@sam821203 sam821203 deleted the refactor/router-lazy-load-and-bundle-split branch March 14, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Router lazy loading and bundle splitting

1 participant