Files

3.6 KiB

Web App

The public website: a Next.js App Router app that server-renders the pages editors publish in Web Manager. 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.

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

npm run dev:web
npm run test:web
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
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/*/<locale>.json files
A new backend read A method on WebRuntimeAPI with its tags, plus a normalizer for the response