From d32ccadeb73e62ca0978ec1f7eb9144cc90d73fc Mon Sep 17 00:00:00 2001 From: iammukeshm Date: Wed, 4 Nov 2026 08:48:16 +0530 Subject: [PATCH 1/2] docs: route translation catalogs load with their route; changelog for the bundle tuning (#1462) --- src/content/docs/changelog/index.mdx | 4 ++++ .../docs/cross-cutting-concerns/internationalization.mdx | 2 +- src/content/docs/guides/add-a-dashboard-page.mdx | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/content/docs/changelog/index.mdx b/src/content/docs/changelog/index.mdx index 008d87fb..fe048166 100644 --- a/src/content/docs/changelog/index.mdx +++ b/src/content/docs/changelog/index.mdx @@ -11,6 +11,10 @@ seo: Notable changes to the kit, newest first. +## 2026-11-04 + +- **Frontends: smaller initial load after the Vite 8 upgrade (perf).** Rolldown had split the code each app loads up front into 45 (admin) and 68 (dashboard) small chunks, so initial load grew past the Vite 7 numbers. `vite.config.ts` now groups everything the entry statically imports into one `vendor` and one `app` chunk. Only the app shell's `en-US` translation namespaces ship in the main bundle, and a route's own namespaces load alongside its page chunk. Admin now loads the SignalR client on first connect, as the dashboard already did. Initial load (gzip) drops from 265.4 to 225.1 KiB in admin and from 271.3 to 229.9 KiB in the dashboard, below the pre-upgrade 242.9 and 245.2 KiB. **If you forked the apps:** a new route namespace goes in the lazy catalog map in `src/i18n.ts` and is listed on its routes, as in `lazyNamed(importer, name, ["ns"])`. The new `tests/i18n/route-namespaces.spec.ts` fails when a route misses one. See [Internationalization](/docs/cross-cutting-concerns/internationalization/#catalogs), [#1462](https://github.com/fullstackhero/dotnet-starter-kit/issues/1462) and [#1466](https://github.com/fullstackhero/dotnet-starter-kit/pull/1466). + ## 2026-11-03 - **Dependencies: the React apps are on the latest stable versions (chore).** Both apps move to **Vite 8** (Rolldown) with `@vitejs/plugin-react` 6, **TypeScript 6.0.3** (held below 6.1 until `typescript-eslint` supports 7), **ESLint 10**, `eslint-plugin-react-hooks` 7, **lucide-react 1.x**, React 19.3 and the current minors of TanStack Query, React Router, Radix, Tailwind 4.3, SignalR and i18next; the admin app moves to **zod 4** with `@hookform/resolvers` 7. The container build and Frontend CI run on **Node 26** (LTS), the runtime image is pinned to `nginx:1.31.8-alpine`, and `engines.node` is now `^20.19.0 || >=22.13.0`. If you forked the apps: `tsconfig` drops the deprecated `baseUrl`; `eslint-plugin-jsx-a11y` (no ESLint 10 release yet) is replaced by the `eslint-plugin-jsx-a11y-x` fork under the same `jsx-a11y` rule names; and the React Compiler-aware hooks rules now fail the lint on `setState` called directly in an effect, refs read or written during render, and components created during render, so your own pages may need the same fixes. zod 4 deprecates `.email()`/`.url()` and `{ message }` in favour of `z.email()`/`z.url()` and `{ error }`. The admin dev proxy now forwards SignalR's WebSocket upgrade. See [Development](/docs/frontend/development/#linting), [Prerequisites](/docs/getting-started/prerequisites/) and PR [#1461](https://github.com/fullstackhero/dotnet-starter-kit/pull/1461); part of [#1453](https://github.com/fullstackhero/dotnet-starter-kit/issues/1453). diff --git a/src/content/docs/cross-cutting-concerns/internationalization.mdx b/src/content/docs/cross-cutting-concerns/internationalization.mdx index 3ca70c1b..555341d4 100644 --- a/src/content/docs/cross-cutting-concerns/internationalization.mdx +++ b/src/content/docs/cross-cutting-concerns/internationalization.mdx @@ -124,7 +124,7 @@ for example `src/locales/en-US/common.json` and `src/locales/pt-BR/common.json`. Only `en-US` has to be complete. A translation may lag behind: a key missing from `pt-BR` (or a whole namespace without a `pt-BR` file) renders the English string, because the fallback chain always ends in `en-US`, even on a deployment whose default language is `pt-BR` (`fallbackChain` in `src/lib/i18n-fallback.ts`). Each app's `tests/i18n/parity.spec.ts` fails only on a `pt-BR` key that `en-US` does not have (an orphan, usually a renamed or deleted key) and on a `pt-BR` value whose `{{placeholders}}` differ from the English one, so a contributor adding English strings never has to write Portuguese to get a green build. The backend catalogs follow the same rule. -Only `en-US` is bundled with the app. Every other language is a single chunk, built from `src/locales/.ts` (an `import.meta.glob` over that locale's JSON files), that loads on demand: before the first render when the active language is not `en-US`, so a Portuguese user never sees English first, and before every switch, which goes through `changeLanguage` in `src/i18n.ts`. When the chunk cannot be loaded, the switcher keeps the current language and does not save the choice to the profile. +Only `en-US` is bundled with the app, and only the namespaces the app shell renders with (layout, sign-in, banners, the notification bell; admin also bundles its landing dashboard). `src/i18n.ts` keeps those in an eager map. Every other `en-US` namespace sits in a lazy map as its own small chunk, and the route that uses it lists it in `lazyNamed(importer, name, ["ns"])` in `src/routes.tsx`, which loads it in parallel with the page's chunk, so the page never renders before its text is there. Each app's `tests/i18n/route-namespaces.spec.ts` walks every route's imports and fails when a page, or a component it uses, needs a namespace its route doesn't list. Every other language is a single chunk, built from `src/locales/.ts` (an `import.meta.glob` over that locale's JSON files), that loads on demand: before the first render when the active language is not `en-US`, so a Portuguese user never sees English first, and before every switch, which goes through `changeLanguage` in `src/i18n.ts`. When the chunk cannot be loaded, the switcher keeps the current language and does not save the choice to the profile. ### Keeping strings translated diff --git a/src/content/docs/guides/add-a-dashboard-page.mdx b/src/content/docs/guides/add-a-dashboard-page.mdx index a141ce64..f565043e 100644 --- a/src/content/docs/guides/add-a-dashboard-page.mdx +++ b/src/content/docs/guides/add-a-dashboard-page.mdx @@ -147,12 +147,14 @@ mutation.mutate({ text, clientId: crypto.randomUUID() }); Every page is code-split via the `lazyNamed` helper (adapts named exports to `React.lazy`'s default contract) and wrapped in `withSuspense` for a per-route skeleton fallback. Add the child route under `AppShell` - which already sits inside `ProtectedRoute`: ```tsx -const ProjectsPage = lazyNamed(() => import("@/pages/projects/projects"), "ProjectsPage"); +const ProjectsPage = lazyNamed(() => import("@/pages/projects/projects"), "ProjectsPage", ["projects"]); // inside the AppShell children array: { path: "projects", element: withSuspense() }, ``` +The third argument lists the page's translation namespaces. Route catalogs are not in the main bundle: add `projects: () => import("@/locales/en-US/projects.json")` to `lazyCatalogs` in `src/i18n.ts`, and `lazyNamed` loads it alongside the page chunk. Shell namespaces (`common`, `auth`, `notifications`) are always loaded and are not listed. `tests/i18n/route-namespaces.spec.ts` fails if the page, or a component it imports, uses a namespace the route doesn't list. See [Internationalization](/docs/cross-cutting-concerns/internationalization/#catalogs). + There is **no per-route permission guard** in the dashboard - `ProtectedRoute` is auth-only. Don't port admin's `RouteGuard` here; the server returns 403 for anything the user can't do, and the nav hides entries they can't reach (next step). ## Step 4 - Sidebar nav entry (`src/components/layout/nav-data.ts`) From cf0fef5d16b924c0f867b25797fa5499c679e85c Mon Sep 17 00:00:00 2001 From: iammukeshm Date: Wed, 4 Nov 2026 08:51:55 +0530 Subject: [PATCH 2/2] docs(changelog): date the entry for its merge --- src/content/docs/changelog/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/changelog/index.mdx b/src/content/docs/changelog/index.mdx index fe048166..e1df1769 100644 --- a/src/content/docs/changelog/index.mdx +++ b/src/content/docs/changelog/index.mdx @@ -1,6 +1,6 @@ --- title: Overview -lastUpdated: 2026-11-03 +lastUpdated: 2026-11-04 description: Release notes and version history for fullstackhero. sidebar: order: 1