♻️ refactor: migrate frontend from Next.js App Router to Vite SPA#12404
♻️ refactor: migrate frontend from Next.js App Router to Vite SPA#12404
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Reviewer's GuideMigrates the LobeChat frontend from Next.js App Router to a Vite + React Router SPA while keeping the Next.js backend, by introducing a SPA entry and global provider, a Next.js catch-all route that serves Vite-built HTML with runtime config injection, refactoring the Next-specific abstraction layer and third‑party integrations to framework-agnostic adapters, updating env handling, and wiring new build/dev/Docker flows for dual desktop/mobile bundles. Sequence diagram for SPA HTML request via Next.js catch-allsequenceDiagram
participant U as Browser
participant M as Middleware_define_config
participant R as SPACatchAllRoute
participant C as getServerGlobalConfig
participant T as spaHtmlTemplates_or_ViteDev
U->>M: GET /chat
M->>M: Check path
M->>M: isNextjsRoute = nextjsOnlyRoutes.some
alt isNextjsRoute true
M-->>U: Rewrite to Next.js auth route
else SPA route
M->>R: NextResponse.next()\npass to (spa)/[[...path]]
R->>R: Detect isMobile from UA
R->>R: Resolve locale\nfrom LOBE_LOCALE_COOKIE or headers
R->>C: getServerGlobalConfig()
C-->>R: GlobalServerConfig
R->>R: getServerFeatureFlagsValue
R->>R: buildAnalyticsConfig
R->>R: buildThemeConfig
R->>R: buildClientEnv
R->>T: getTemplate(isMobile)
alt NODE_ENV=development
T->>T: fetch VITE_DEV_ORIGIN index.html
T->>T: rewriteViteAssetUrls
else production
T->>T: read public/spa/[desktop|mobile]/index.html
end
T-->>R: HTML template
R->>R: serializeForHtml(SPAServerConfig)
R->>R: Inject window.__SERVER_CONFIG__\nReplace LOCALE, DIR placeholders
R-->>U: HTML response
U->>U: Load JS bundle\nBootstrap React Router SPA
end
Class diagram for SPA server config and global providerclassDiagram
class SPAServerConfig {
+AnalyticsConfig analyticsConfig
+SPAClientEnv clientEnv
+GlobalServerConfig config
+Partial~IFeatureFlags~ featureFlags
+boolean isMobile
+string locale
+SPAThemeConfig theme
}
class AnalyticsConfig {
+GoogleConfig google
+PlausibleConfig plausible
+UmamiConfig umami
+ClarityConfig clarity
+PosthogConfig posthog
+ReactScanConfig reactScan
+VercelConfig vercel
+DesktopAnalyticsConfig desktop
}
class SPAClientEnv {
+string marketBaseUrl
+string pyodideIndexUrl
+string pyodidePipIndexUrl
+string s3FilePath
}
class SPAThemeConfig {
+boolean cdnUseGlobal
+string customFontFamily
+string customFontURL
}
class Window {
+SPAServerConfig __SERVER_CONFIG__
}
class SPAGlobalProvider {
+render(children)
}
class ServerConfigStoreProvider {
+featureFlags
+isMobile
+serverConfig
}
class BrowserRouter {
}
class Routes {
}
class desktopRoutes {
}
class mobileRoutes {
}
Window --> SPAServerConfig : holds
SPAServerConfig --> AnalyticsConfig : has
SPAServerConfig --> SPAClientEnv : has
SPAServerConfig --> SPAThemeConfig : has
SPAGlobalProvider --> SPAServerConfig : reads
SPAGlobalProvider --> ServerConfigStoreProvider : composes
SPAGlobalProvider --> BrowserRouter : wraps
BrowserRouter --> Routes : renders
Routes --> desktopRoutes : uses
Routes --> mobileRoutes : uses
File-Level ChangesTips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
cdd8ec4 to
ee6f225
Compare
7577a06 to
d536c4b
Compare
- Clarified the handling of Worker cross-origin issues in dev mode, emphasizing the need for `workerPatch` to wrap cross-origin URLs as blob URLs. - Enhanced the explanation of the dev mode's resource URL rewriting process for better understanding. Signed-off-by: Innei <tukon479@gmail.com>
- Fix Pyodide env var mismatch (NEXT_PUBLIC_PYPI_INDEX_URL → pythonEnv.NEXT_PUBLIC_PYODIDE_PIP_INDEX_URL) - Consolidate python.ts to use pythonEnv instead of direct process.env - Remove NEXT_PUBLIC_ prefix from server-side MARKET_BASE_URL (5 files)
- Add vite.config.ts with dual build (desktop/mobile via MOBILE env) - Add index.html SPA template with __SERVER_CONFIG__ placeholder - Add entry.desktop.tsx and entry.mobile.tsx SPA entry points - Add dev:spa, dev:spa:mobile, build:spa, build:spa:copy scripts - Install @vitejs/plugin-react and linkedom
- Replace next/link with <a> in builtin-tool-web-browsing (4 files, external links) - Replace next/image with <img> in builtin-tool-agent-builder/InstallPlugin.tsx - Add Vite import.meta.env compat for isDesktop in const/version.ts, builtin-tool-gtd, builtin-tool-group-management
- 9 auth files: @/libs/next/navigation → next/navigation - 5 auth files: @/libs/next/Link → next/link - Auth pages remain in Next.js App Router, need direct Next.js imports
- navigation.ts: useRouter/usePathname/useSearchParams/useParams → react-router-dom - navigation.ts: redirect/notFound → custom error throws - navigation.ts: useServerInsertedHTML → no-op for SPA - Link.tsx: next/link → react-router-dom Link adapter (href→to, external→<a>) - Image.tsx: next/image → <img> wrapper with fill/style support - dynamic.tsx: next/dynamic → React.lazy + Suspense wrapper
- Create SPAServerConfig type (analyticsConfig, clientEnv, theme, featureFlags, locale) - Add window.__SERVER_CONFIG__ and __MOBILE__ to global.d.ts - Create SPAGlobalProvider (client-only Provider tree mirroring GlobalProvider) - Includes AuthProvider for user session support - Update entry.desktop.tsx and entry.mobile.tsx to wrap with SPAGlobalProvider
- Create (spa)/[[...path]]/route.ts for serving SPA HTML - Dev mode: proxy Vite dev server, rewrite asset URLs, inject Worker patch - Prod mode: read pre-built HTML templates - Build SPAServerConfig with analytics, theme, clientEnv, featureFlags - Update middleware to pass SPA routes through to catch-all
SPA pages are all public (no sensitive data in HTML). Auth is handled client-side by SPAGlobalProvider's AuthProvider. Only Next.js auth routes and API endpoints go through session checks.
- Google.tsx: replace @next/third-parties/google with direct gtag script - ReactScan.tsx: replace react-scan/monitoring/next with generic script - Desktop.tsx: replace next/script with native script injection
Replace framework-specific env validation with framework-agnostic version. Add clientPrefix where client schemas exist.
Use client-side react-markdown for MDX rendering instead of Next.js RSC-dependent next-mdx-remote.
- build:docker now includes SPA build + copy steps - dev defaults to Vite SPA, dev:next for Next.js backend - Dockerfile copies public/spa/ assets for production - Add public/spa/ to .gitignore (build artifact)
- Delete [variants] page.tsx, error.tsx, not-found.tsx, loading.tsx - Delete root loading.tsx and empty [[...path]] directory - Delete unused loaders directory - Remove @serwist/next PWA wrapper from Next.js config
…ded port 3015 Replace hardcoded http://localhost:3015 with process.env.ELECTRON_RENDERER_URL injected by electron-vite dev server. Clean up stale Next.js references.
HTML entry ../../src/entry.desktop.tsx resolves to /src/entry.desktop.tsx in URL space, which Vite cannot find within apps/desktop/ root. Add a local shim that imports across root via module resolver instead.
…nfig Deduplicate plugins (nodeModuleStub, platformResolve, tsconfigPaths) and define (__MOBILE__, __ELECTRON__, process.env) between root vite.config.ts and electron.vite.config.ts renderer section.
…nfig sharedRendererPlugins now includes react, codeInspectorPlugin alongside nodeModuleStub, platformResolve, tsconfigPaths. Add sharedOptimizeDeps for pre-bundling list. Both root and electron configs consume shared only.
…esolution import.meta.glob with absolute paths (e.g. /node_modules/antd/...) resolved within apps/desktop/ instead of monorepo root. Change renderer root to ROOT_DIR, add electronDesktopHtmlPlugin middleware to rewrite / to /apps/desktop/index.html, and remove the now-unnecessary renderer-entry.ts shim.
Signed-off-by: Innei <tukon479@gmail.com>
Signed-off-by: Innei <tukon479@gmail.com>
c695503 to
a40b10b
Compare
There was a problem hiding this comment.
Sorry @Innei, your pull request is larger than the review limit of 150000 diff characters
Signed-off-by: Innei <tukon479@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a40b10bbd2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "build:vercel": "tsx scripts/prebuild.mts && npm run lint:ts && npm run lint:style && npm run type-check:tsc && npm run lint:circular && cross-env NODE_OPTIONS=--max-old-space-size=6144 next build --webpack && npm run postbuild", | ||
| "build": "bun run build:spa && bun run build:spa:copy && bun run build:next", | ||
| "build:analyze": "NODE_OPTIONS=--max-old-space-size=81920 ANALYZE=true next build", | ||
| "build:docker": "npm run prebuild && bun run build:spa && bun run build:spa:copy && NODE_OPTIONS=--max-old-space-size=8192 DOCKER=true next build && npm run build-sitemap", |
There was a problem hiding this comment.
Restore missing prebuild script for build:docker
The build:docker command now invokes npm run prebuild, but this commit removed the prebuild entry from scripts. In a clean checkout, npm run build:docker exits immediately with Missing script: "prebuild", so Docker image builds are blocked before any SPA/Next build steps run.
Useful? React with 👍 / 👎.
package.json
Outdated
| "build:analyze": "NODE_OPTIONS=--max-old-space-size=81920 ANALYZE=true next build", | ||
| "build:docker": "npm run prebuild && bun run build:spa && bun run build:spa:copy && NODE_OPTIONS=--max-old-space-size=8192 DOCKER=true next build && npm run build-sitemap", | ||
| "build:next": "cross-env NODE_OPTIONS=--max-old-space-size=8192 next build", | ||
| "build:spa": "rm -rf public/spa && NODE_OPTIONS=--max-old-space-size=8192 vite build && tsx scripts/generateSpaTemplates.mts", |
There was a problem hiding this comment.
Build mobile SPA assets before template generation
This build:spa script runs a single default vite build and then generates templates that read dist/mobile/index.html (and later copies dist/mobile/assets). Because no mobile build is triggered here, clean builds fail with missing dist/mobile artifacts (or silently use stale mobile outputs left from prior runs), which makes the SPA build pipeline unreliable.
Useful? React with 👍 / 👎.
- Changed the path in .gitignore and related files from [locale] to [variants] for SPA templates. - Updated index.html to set body height to 100%. - Cleaned up package.json by removing unused dependencies and reorganizing devDependencies. - Refactored RendererUrlManager to use a constant for SPA entry HTML path. - Removed obsolete route.ts file from the SPA structure. - Adjusted proxy configuration to reflect the new SPA path structure. Signed-off-by: Innei <tukon479@gmail.com>
- Modified the build script in package.json to add the mobile SPA build step. - Ensured the build process accommodates both desktop and mobile SPA versions. Signed-off-by: Innei <tukon479@gmail.com>
- Modified the build script in package.json to ensure the SPA copy step runs after the build. - Updated file encoding in generateSpaTemplates.mts from 'utf-8' to 'utf8' for consistency. Signed-off-by: Innei <tukon479@gmail.com>
- Fixed the Blob import syntax in route.ts to ensure proper module loading. - Updated the global server configuration type in global.d.ts for improved type safety. Signed-off-by: Innei <tukon479@gmail.com>
- Modified the mock implementation in RendererUrlManager.test.ts to check for the updated file path '/mock/export/out/apps/desktop/index.html'. - Adjusted the expected resolved path in the test to match the new structure. Signed-off-by: Innei <tukon479@gmail.com>
- Deleted the catch-all example file `catch-all.eg.ts` to streamline the codebase. - Updated import paths in `ClientResponsiveLayout.tsx` and `ClientResponsiveContent/index.tsx` to use the new dynamic import location. - Added type declarations for HTML templates in `spaHtmlTemplates.d.ts`. - Adjusted `tsconfig.json` to include the updated file structure. - Enhanced type definitions in `global.d.ts` and fixed locale loading in `locale.vite.ts`. Signed-off-by: Innei <tukon479@gmail.com>
- Deleted the `build:vercel` script from package.json to streamline the build process. - Ensured the remaining build scripts are organized and relevant. Signed-off-by: Innei <tukon479@gmail.com>
- Changed the build input path in vite.config.ts to conditionally use 'index.mobile.html' for mobile builds, enhancing support for mobile SPA versions. Signed-off-by: Innei <tukon479@gmail.com>
Summary
Migrate the LobeChat frontend from Next.js App Router client-side rendering to a standalone Vite SPA, while keeping Next.js solely for server-side API routes, auth, and SEO pages. This decouples the heavy client bundle from Next.js, enables faster HMR via Vite, and unifies the web + Electron desktop renderer under a shared Vite config.
Phases
Phase 1 — Environment Variable Cleanup
@t3-oss/env-nextjs→@t3-oss/env-corePhase 2 — Vite Project Setup
vite.config.tswith React plugin, path aliases, andimport.meta.globlocale loadingdev:nexttask inturbo.jsonfor parallel Vite + Next dev startupbase: '/spa/'for production builds; auto-generatespaHtmlTemplatesfrom build outputPhase 3 — First-Party Package Decoupling
@lobechat/*packagesnext-mdx-remote/rscwithreact-markdownPhase 4 — Route & Navigation Migration
next/navigation+next/linkreact-router-dom/ vanilla React(spa)route group →spasegment; rewrite SPA routes via Next.js middlewarePhase 5 — SPA Runtime
SPAGlobalProvider(theme, i18n, auth context, router)index.htmlfor SPA dev mode[locale]segment withforce-staticand SEO meta generationPhase 6 — Electron Desktop Integration
isDesktop→__ELECTRON__compile-time constantsharedRendererConfig)electron-viteconfig; useELECTRON_RENDERER_URLinstead of hardcoded port.desktopsuffix files for eager i18n loadingelectron-builderfiles config for Vite renderer outputMisc
@ast-grep/napidependencyFile Changes
src/,apps/desktop/,packages/,vite.config.ts,turbo.json,next.config.ts,DockerfileType of Change
Testing
import.meta.globvalidatedChecklist
/spa/prefixspaHtmlTemplates