# Web App The public website: a Next.js App Router app that server-renders the pages editors publish in [Web Manager](../../docs/core/web-manager/Readme.md). Everything visitors see comes from the backend's public API. Package: `@crux-kit/web-app`. Architecture — request flow, layers, backend boundary, rendering registries, i18n, SEO — is documented in the [Next.js web architecture](../../docs/architecture/web/Readme.md). | Tech | Version | | ---------- | ---------------------------------------- | | Next.js | 16 (App Router, React Server Components) | | React | 19 | | next-intl | 4 — locale routing and translations | | Tailwind | 4 — CSS-first, no config file | | TypeScript | 5.9, `strict`, `noEmit` | | Vitest | 4 — node environment, no DOM tests | ## Configuration | Variable | Required | Purpose | | ------------------- | ----------------------- | --------------------------------------------------------------------- | | `BACKEND_API_URL` | yes — throws without it | Backend origin. No `/web-manager/public` suffix. | | `SITE_URL` | in production | Canonical origin. Missing → `http://localhost:3000` + a logged error. | | `REVALIDATE_SECRET` | for purges to work | Must equal the backend's `WEB_REVALIDATE_SECRET`. | Copy `.env.example` to `.env`. In Docker the backend URL must use the compose service name (`http://backend:3333`), not `localhost`. ## Development ```bash npm run dev:web ``` ```bash npm run test:web ``` ```bash npm run build:web ``` `build` runs `tsc --noEmit` before `next build`, so a type error fails the build. Under Docker the `web` service runs `npm run dev` on port 3000 with `.next` on an anonymous volume. That volume survives rebuilds and is the usual cause of a stale bundle — recreate with `docker compose up -d --force-recreate --renew-anon-volumes web`. Path alias: `#*` maps to the app root. `vitest.config.ts` mirrors it by hand (Vitest ignores `tsconfig` paths) and stubs `server-only`. ## Tests `tests/` covers the pure logic (normalization, cache tags, registries, metadata, sitemap, site URL, structured data) in a node environment — component rendering is not covered and would need `@testing-library/react`. `registry.test.ts` keeps a hand-mirrored list of the backend's block and page types and asserts the registries match exactly — the tripwire for a block the CMS can publish but the site cannot draw. ## Adding Things | Task | Where | | ---------------------------- | --------------------------------------------------------------------------------------------------------- | | A new CMS block or page type | Spans three apps — follow [Web Manager → Extending](../../docs/core/web-manager/integration.md#extending) | | A non-CMS route | A folder under `app/[locale]/`; keep the logic in `web-runtime/` | | A shared UI primitive | `shared/ui/` — content-agnostic only | | A new locale | Add it to `site.config.ts` and add the five `i18n/translations/*/.json` files | | A new backend read | A method on `WebRuntimeAPI` with its tags, plus a normalizer for the response |