diff --git a/frontend/src/app/routes.tsx b/frontend/src/app/routes.tsx
index 25e88c8..6b820c7 100644
--- a/frontend/src/app/routes.tsx
+++ b/frontend/src/app/routes.tsx
@@ -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: ,
children: [
{ path: '/', element: },
- { path: '/login', element: },
- { path: '/register', element: },
- { path: '/download', element: },
- { path: '/themes', element: },
- { path: '/u/:id', element: },
- { path: '/showcase', element: },
- { path: '/showcase/:id', element: },
+ { 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: ,
children: [
- { path: '/link', element: },
- { path: '/account', element: },
- { path: '/configs', element: },
+ { 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: },
diff --git a/frontend/src/features/auth/tests/pages.test.tsx b/frontend/src/features/auth/tests/pages.test.tsx
index 13c18a5..04d7f87 100644
--- a/frontend/src/features/auth/tests/pages.test.tsx
+++ b/frontend/src/features/auth/tests/pages.test.tsx
@@ -42,7 +42,12 @@ test('registering after a redirect lands back where the visitor was going', asyn
})
const { router } = renderApp(routes, '/link')
await user.click(await screen.findByRole('link', { name: 'Регистрация' }))
- await user.type(await screen.findByLabelText('Почта'), 'a@b.c')
+ // /login and /register are lazy-loaded and share the "Почта" email label, so the
+ // previous (login) page can still be mounted for a tick while the register chunk
+ // resolves. Wait for a register-only marker before querying its email field, or a
+ // stale reference to the login field (about to unmount) can end up typed into instead.
+ await screen.findByLabelText('Ник на сайте')
+ await user.type(screen.getByLabelText('Почта'), 'a@b.c')
await user.type(screen.getByLabelText('Ник на сайте'), 'Rider')
await user.type(screen.getByLabelText('Пароль'), 'correct-horse')
await user.click(screen.getByRole('button', { name: 'Создать аккаунт' }))
diff --git a/frontend/src/test/setup.ts b/frontend/src/test/setup.ts
index 41d921d..5e9f4fa 100644
--- a/frontend/src/test/setup.ts
+++ b/frontend/src/test/setup.ts
@@ -1,11 +1,16 @@
import '@testing-library/jest-dom/vitest'
import 'fake-indexeddb/auto'
-import { cleanup } from '@testing-library/react'
+import { cleanup, configure } from '@testing-library/react'
import { afterEach, vi } from 'vitest'
import { initI18n } from '@app/i18n'
initI18n('ru')
+// Route pages load via dynamic import() (see src/app/routes.tsx), and under a loaded
+// test run (many jsdom workers in parallel) that first chunk resolution can take longer
+// than the default 1000ms findBy/waitFor timeout. Give async queries more headroom.
+configure({ asyncUtilTimeout: 5000 })
+
afterEach(() => cleanup())
afterEach(() => vi.unstubAllGlobals())
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 8459305..e004031 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -35,5 +35,8 @@ export default defineConfig({
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
css: false,
+ // Route pages now load via dynamic import() (src/app/routes.tsx); the default 5000ms
+ // per-test budget can be tight for a route-transition test under a loaded parallel run.
+ testTimeout: 10000,
},
})