Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/content/docs/changelog/index.mdx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/<locale>.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/<locale>.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

Expand Down
4 changes: 3 additions & 1 deletion src/content/docs/guides/add-a-dashboard-page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<ProjectsPage />) },
```

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`)
Expand Down