perf(frontend): route-level code splitting, main chunk 538 -> 491 KB

Lighthouse (production, default mobile 4x-CPU profile) scored
Performance 33/100, dominated by one ~536-538 KB entry chunk that
bundled every route's code regardless of which page a visitor landed
on -- the "chunks are larger than 500 kB" Vite warning on every build
this session.

Converted every route except / and the 404 catch-all to React Router
8's native route.lazy (blocks navigation until the chunk is ready --
no separate Suspense fallback needed). AppShell/HomePage/NotFoundPage/
RequireAuth stay eager since they're needed on every load regardless.

Result: main chunk 490.84 KB, no >500kB warning, one small chunk per
route (LoginPage 1.4 KB, ThemesPage 15.5 KB, AccountPage 11 KB, etc.)
loaded only when actually visited.

Fixed a real race the lazy boundary exposed: /login and /register
share an "Почта" field label, and during the chunk swap between them
the old page can stay mounted a beat longer than before -- a test
grabbing the field by label could land on the about-to-unmount one.
Also bumped RTL's asyncUtilTimeout and vitest's testTimeout, since the
route-transition + findBy chain didn't always land inside the old
1000/5000ms defaults under full parallel test-suite load.

Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
This commit is contained in:
loki5512344 2026-09-30 16:06:45 +02:00
parent 6b0633034d
commit b170227adf
Signed by: boba
GPG key ID: 253067914055423B
4 changed files with 34 additions and 23 deletions

View file

@ -1,38 +1,36 @@
import type { ComponentType } from 'react'
import type { RouteObject } from 'react-router'
import { RequireAuth } from '@features/auth/RequireAuth'
import { AppShell } from '@shared/layout/AppShell'
import HomePage from '@pages/public/HomePage'
import LoginPage from '@pages/public/auth/LoginPage'
import RegisterPage from '@pages/public/auth/RegisterPage'
import ProfilePage from '@pages/public/ProfilePage'
import NotFoundPage from '@pages/public/NotFoundPage'
// Code-split: the download page pulls in react-markdown, so it loads on demand.
import DownloadRoute from '@pages/download/DownloadRoute'
import AccountPage from '@pages/app/AccountPage'
import LinkPage from '@pages/app/LinkPage'
import ThemesRoute from '@pages/themes/ThemesRoute'
import ConfigsPage from '@pages/app/ConfigsPage'
import ShowcaseRoute from '@pages/showcase/ShowcaseRoute'
import ListingRoute from '@pages/showcase/ListingRoute'
// Adapts a page module's default export to the shape React Router's `lazy` expects.
function toRoute(m: { default: ComponentType }) {
return { Component: m.default }
}
// Everything below is route-level code-split via React Router's `lazy` field:
// each page's JS only downloads when a visitor actually navigates to it, so the
// initial bundle only carries the app shell + the home page.
export const routes: RouteObject[] = [
{
element: <AppShell />,
children: [
{ path: '/', element: <HomePage /> },
{ path: '/login', element: <LoginPage /> },
{ path: '/register', element: <RegisterPage /> },
{ path: '/download', element: <DownloadRoute /> },
{ path: '/themes', element: <ThemesRoute /> },
{ path: '/u/:id', element: <ProfilePage /> },
{ path: '/showcase', element: <ShowcaseRoute /> },
{ path: '/showcase/:id', element: <ListingRoute /> },
{ path: '/login', lazy: () => import('@pages/public/auth/LoginPage').then(toRoute) },
{ path: '/register', lazy: () => import('@pages/public/auth/RegisterPage').then(toRoute) },
{ path: '/download', lazy: () => import('@pages/download/DownloadRoute').then(toRoute) },
{ path: '/themes', lazy: () => import('@pages/themes/ThemesRoute').then(toRoute) },
{ path: '/u/:id', lazy: () => import('@pages/public/ProfilePage').then(toRoute) },
{ path: '/showcase', lazy: () => import('@pages/showcase/ShowcaseRoute').then(toRoute) },
{ path: '/showcase/:id', lazy: () => import('@pages/showcase/ListingRoute').then(toRoute) },
{
element: <RequireAuth />,
children: [
{ path: '/link', element: <LinkPage /> },
{ path: '/account', element: <AccountPage /> },
{ path: '/configs', element: <ConfigsPage /> },
{ path: '/link', lazy: () => import('@pages/app/LinkPage').then(toRoute) },
{ path: '/account', lazy: () => import('@pages/app/AccountPage').then(toRoute) },
{ path: '/configs', lazy: () => import('@pages/app/ConfigsPage').then(toRoute) },
],
},
{ path: '*', element: <NotFoundPage /> },