Projektstart mit Next.js
Das Fundament ist am günstigsten am ersten Tag zu legen: Jede Regel muss später in Code eingearbeitet werden, der sie verletzt. Wenn die Vorlage frontend-starter existiert – beginne mit einer Kopie der Vorlage: Darin sind die untenstehenden Schritte bereits umgesetzt und getestet, und dieses Dokument dient als Erklärung der Entscheidungen und als Audit-Checkliste. Dieses Dokument gibt die Reihenfolge der Schritte und den minimalen Umfang vor. Wie der eigentliche Code geschrieben wird, steht in der Skill fsd-frontend (sofern im Projekt vorhanden), die Autorisierung in frontend-auth-session, die Prüfung vor dem Commit in anti-slop-review. Fertige Konfigurationen finden sich in references/configs.md, die Aufteilung von Next + FSD mit Codebeispielen in references/next-structure.md.
Priorität der Regeln. Explizite Anweisungen des Benutzers und Agent-Dateien des Projekts (AGENTS.md, CLAUDE.md) haben Vorrang vor diesem Dokument. Bibliotheksversionen ändern sich: Bevor eine Konfiguration kopiert wird, ist sie mit der Dokumentation der installierten Version abzugleichen. Es darf nicht behauptet werden, dass sie funktioniert, bevor sie nicht ausgeführt wurde.
Fakten über Next.js stammen aus der Dokumentation im Paket, nicht aus diesem Dokument und nicht aus dem Gedächtnis. Ab Version 16.2 liegt die Dokumentation in node_modules/next/dist/docs/, und die AGENTS.md des Projekts verweist darauf. Bei Abweichungen von den Beispielen hier gilt die Dokumentation der installierten Version.
Bezeichnungen: MUST – darf nicht verletzt werden; SHOULD – standardmäßig ja, Abweichung erfordert einen Grund.
1. Entscheidungen vor dem Code
Jede Entscheidung aus der Tabelle wird im README (Abschnitt „Entscheidungen“) oder in einem ADR mit Begründung festgehalten. Eine Abweichung vom Standardwert ohne dokumentierten Grund wird nicht vorgenommen.
| Frage | Standard | Wann anders |
|---|---|---|
| Next.js oder SPA | Next.js, wenn mindestens eines von SSR/SEO, BFF (Geheimnisse, HttpOnly-Cookie), Route-Handlern benötigt wird | Interne Anwendung hinter Login ohne SEO und ohne BFF → Vite + React Router: einfachere Build- und Deployment-Pipeline |
| Router | App Router | Pages Router – nur in einem bestehenden Projekt |
| Monorepo | Nein, eine Anwendung | Wenn eine zweite Anwendung oder ein Paket tatsächlich auftaucht → pnpm workspaces; Nx/Turborepo – wenn Build oder CI langsam werden |
| Multi-Zones, Mikrofrontends | Nein | Unabhängige Teams mit unabhängigen Releases |
| Serverseitige Daten | Server Components für den initialen Load; TanStack Query für interaktive Client-Daten und Mutationen | Reine Client-Anwendung → nur TanStack Query |
| Texte und i18n | Übersetzungsmodul von Tag eins (next-intl oder Äquivalent), auch für nur ein Gebietsschema, wenn das Projekt fsd-frontend folgt | Explizite Entscheidung „ohne i18n“ im README festgehalten |
| Paketmanager | pnpm, Version in packageManager | — |
| Deployment | Sofort entscheiden: Vercel, Node-Server (output: "standalone") oder Docker | Davon hängen env, Cache und CI ab |
MUST: Keine Technologie „für die Zukunft“ hinzufügen. Monorepo, zweiter Router, Storybook, State-Manager, MSW „als Backend-Typ“ tauchen auf, wenn sie einen ersten tatsächlichen Nutzer haben.
2. Reihenfolge der Schritte
- Projekt erstellen (
pnpm create next-app@latest): TypeScript, ESLint, Tailwind, App Router.packageManager,engines.node(Next 16 benötigt Node 20.9+) und.nvmrcfestlegen. - Generator-Vorlage sofort entfernen: Startseite, SVGs aus
public/, das README aus der Vorlage, Demo-Styles. README durch das Gerüst ausreferences/configs.mdersetzen. Eine zurückgelassene Vorlage überlebt bis zur Produktion.AGENTS.mdundCLAUDE.mdvoncreate-next-appwerden nicht entfernt: Der Block zwischen<!-- BEGIN:nextjs-agent-rules -->und<!-- END:nextjs-agent-rules -->verweist Agenten auf die Dokumentation der installierten Version;next devpflegt ihn selbst. Eigene Projektregeln werden außerhalb dieser Markierungen ergänzt. - Striktes TypeScript (Abschnitt 4 von
references/configs.md) und Skripttypecheck(tsc --noEmit). - FSD-Struktur über Next (Abschnitt 3 unten).
- Linter und Formatierung: ESLint flat config von
eslint-config-next+ type-aware Regeln + Grenz-Linter + Prettier. Next 16 hatnext lintentfernt und lintet nicht beinext build– der Linter wird als separates Skript und in CI ausgeführt. - Env-Konfiguration (Abschnitt 4).
- Fehlermodell und API-Client in
shared/api(Abschnitt 5). - Provider im Layer
_app: QueryClient, Theme, i18n, globale Services (Toast, Confirm) als Port inshared+ Implementierung in_app. - UI-Grundlage (Abschnitt 6).
- Tests: Vitest + Testing Library + MSW, Playwright für e2e (Abschnitt 7).
- Git-Hooks: husky + lint-staged (eslint --fix, prettier) + commitlint.
- CI: lint, typecheck, test, build bei jedem Push und PR (Abschnitt 8).
- Sicherheit standardmäßig (Abschnitt 9).
- README mit dem gefüllt, was bereits wahr ist (Abschnitt 10), und Definition of Done durchlaufen (Abschnitt 11).
Schritte 1–3 sind ein separater Commit „scaffold“: So ist in der Historie sichtbar, was der Generator gemacht hat und was du.
3. FSD über dem Next-Routing
Gemäß dem offiziellen FSD-Guide für App Router: Der Next-Ordner app/ liegt im Projektstamm, die FSD-Layer in src/, und die Layer app und pages erhalten das Präfix _, um Verwechslungen mit den Next-Ordnern zu vermeiden.
app/ Routing Next: nur Routendateien
├── layout.tsx Root-Layout → Provider aus src/_app
├── (auth)/sign-in/page.tsx export { SignInPage as default } from "@/_pages/sign-in"
└── api/auth/login/route.ts Route-Handler (BFF), dünn
proxy.ts Next 16+ (früher middleware.ts), neben app/
src/
├── _app/ Layer app: Provider, globale Styles, Service-Implementierungen
├── _pages/ Layer pages
├── widgets/ (wenn das Projekt sie verwendet)
├── features/
├── entities/
└── shared/{ui,api,lib,config}
MUST:
- Dateien in
app/sind dünn: Re-Export der Seite aussrc/_pages,generateMetadata, Parsen vonparams/searchParams(in Next 15+ sind dasPromise, sie müssenawaited werden) und Übergabe als Props. Keine Logik darin. - Route-Handler und Server Actions sind dünn: Eingabe parsen und validieren → Funktion aus
srcaufrufen → Antwort zurückgeben. Jeder von ihnen ist ein öffentlicher Einstiegspunkt: Er prüft die Autorisierung selbst und vertraut nicht aufproxy.ts. src/appundsrc/pagesnicht erstellen: Next sucht die Dienstordner sowohl im Stamm als auch insrc, und ein Ordner mit diesem Namen wird leicht zu einem zweiten Router.- Der Grenz-Linter kennt die Präfixe: Muster
src/_app,src/_pages/*stattsrc/app,src/pages/*(Konfiguration –fsd-frontend,references/conventions.md, Abschnitt 11). "use client"wird so tief wie möglich im Baum platziert. Eine Seite ist standardmäßig serverseitig, Client-Komponenten sind Blätter mit Interaktivität.- Module mit Geheimnissen beginnen mit
import "server-only", Module mitwindowmitimport "client-only".
Ausführlich und mit Code: references/next-structure.md.
4. Umgebungskonfiguration
MUST:
- Variablen werden in zwei Modulen gelesen:
shared/config/env.server.ts(import "server-only") undshared/config/env.client.ts. Schema –zod, Validierung beim Import: Ein Konfigurationsfehler tritt beim Start auf, nicht bei der ersten Anfrage. Im restlichen Code istprocess.envverboten (Linter-Regel). NEXT_PUBLIC_*werden beim Build in das Bundle eingebettet und nur bei direktem Zugriffprocess.env.NEXT_PUBLIC_X: ohne Destrukturierung undprocess.env[name]. Ein Image für mehrere Umgebungen mit unterschiedlichen öffentlichen Werten kann so nicht gebaut werden – solche Werte werden auf dem Server gelesen und nach unten gereicht.- Geheimnisse – nur ohne das Präfix
NEXT_PUBLIC_. .env.exampleenthält jede Variable mit einem Kommentar und einem sicheren Wert;.env*in.gitignore, außer.env.example. Der Build in CI läuft mit den Werten aus.env.example.
5. Fehlermodell und API-Client
MUST:
- Fehler werden einmal an der Grenze
shared/apizuAppErrormitkindnormalisiert (Modell –fsd-frontend,references/conventions.md, Abschnitt 1). Oberhalb vonshared/apianalysiert niemandResponse, Statuscodes oder Fehlertexte. - Ein einziger
request()überfetch(oder ein generierter Client, der genauso umschlossen ist): Leitetsignalweiter, übersetzt Netzwerkfehler innetwork, HTTP-Status inkind, validiert den Antwortkörper mit einem Schema. Beispiel –references/next-structure.md. - Art des Fehlers über den Code: Status,
extensions.code(GraphQL antwortet mit 200 auch bei Fehlern),codeim Körper. Wenn das Backend nur Text zurückgibt – Zuordnung über Text in einem einzigen Mapper, mit Kommentar und Begründung und Tests. - Die Backend-Antwort wird an der Grenze in einen domänenspezifischen Typ abgebildet; DTOs gelangen nicht in die UI.
- Benutzerdaten werden nicht zwischen verschiedenen Benutzern gecacht: Kein
"use cache"/unstable_cachefür Antworten, die von der Sitzung abhängen. - Der Client wird aus einem Schema (OpenAPI, GraphQL) generiert, falls ein Schema vorhanden ist. Das Schema oder der generierte Code liegen im Repository: CI darf nicht auf das Live-Backend zugreifen.
SHOULD: Interface über dem Transport (AuthApi + Factory) – nur, wenn es bereits einen zweiten Nutzer gibt (fsd-frontend, references/state-and-data.md, 2.1). Ein Konfigurationswert vom Typ type: "mock", der „not implemented“ wirft, wird nicht angelegt.
6. UI-Grundlage
- Design-Tokens – CSS-Variablen in
:root(auch für Dark Mode), eingebunden in Tailwind v4 über@theme. In Komponenten nur Tokens; rohe HEX-Werte und beliebige Werte (text-[#3a3a3a]) – einmalige Ausnahme mit Kommentar. - Klassen existieren. Aus shadcn/ui und Beispielen kopierte Komponenten bringen ihre eigenen Tokens mit (
bg-card,text-muted-foreground). Entweder diese Tokens anlegen oder die Klassen ersetzen – sonst bleibt die Komponente still und leise ohne Styles. - Primitiven in
shared/uibasieren auf einer Headless-Bibliothek (Radix, React Aria) für Barrierefreiheit; Varianten –cva, Klassen-Kombination –cn(clsx+tailwind-merge). - Schriftarten –
next/font, Bilder –next/imagemit Maßen,images.remotePatternssind explizit aufgelistet. - Storybook – erst, wenn das UI-Kit von mehreren Anwendungen oder Teams wiederverwendet wird, nicht früher.
7. Tests
- Vitest + Testing Library + jsdom + MSW mit
onUnhandledFrame: "error"(MSW 3; in MSW 2 –onUnhandledRequest). Konfiguration –references/configs.md. - Vitest kann keine
asyncServer Components rendern. Logik daraus in Funktionen auslagern und mit Unit-Tests testen, die Seite selbst in e2e. - Playwright für 3–10 kritische Pfade. In CI laufen e2e gegen ein vorhersagbares Backend (Mock-Server, Test-DB-Stand), nicht gegen einen gemeinsamen Stand, wo Tests fremde Daten ändern.
- Der erste Test wird am Tag null geschrieben: Er prüft, dass die Testinfrastruktur funktioniert, und dient als Vorlage.
8. CI
MUST: Bei jedem Push in den Hauptbranch und bei jedem PR: lint, typecheck, test, build. GitHub Actions Vorlage – references/configs.md. Für öffentliche Repositories sind Actions kostenlos, für private gibt es ein monatliches Minutenkontingent.
- Der Build in CI läuft mit
.env.example, ohne Geheimnisse. - e2e – als separater Job, wenn ein isoliertes Backend vorhanden ist; bis dahin ein expliziter Vermerk im README, warum sie nicht im CI sind.
- Das CI-Badge im README erscheint zusammen mit dem Workflow.
9. Sicherheit standardmäßig
poweredByHeader: false; Header überheaders()innext.config.ts:X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin,X-Frame-Options: DENY(oderframe-ancestorsin CSP). CSP mit Nonce – eine separate Aufgabe, aber die Entscheidung „vorhanden / nicht vorhanden und warum“ wird sofort festgehalten.- Server Actions und Route-Handler – öffentliche Endpunkte: Eingabevalidierung mit Schema und Berechtigungsprüfung in jedem.
dangerouslySetInnerHTML– nur mit Sanitizer und Kommentar mit Begründung.- Personenbezogene Daten und Tokens werden nicht geloggt.
- Autorisierung – über
frontend-auth-session.
10. README
Gerüst – references/configs.md. Es gilt eine Regel: Im README steht nur, was für den Code in diesem Commit wahr ist. Jeder Abschnitt wird im selben Commit aktualisiert wie der Code, den er beschreibt. Pläne – in einem separaten Abschnitt „Als nächstes“ mit dem Hinweis, dass sie nicht umgesetzt sind.
11. Definition of Done für den Start
- Generator-Vorlage entfernt; README ist ein Gerüst, das mit der Wahrheit gefüllt ist.
-
packageManager,engines.node,.nvmrcfestgelegt. - TS strict;
typecheck,lint,test,buildlaufen lokal durch (ausgeführt, nicht angenommen). - FSD-Struktur mit Präfixen
_app/_pages; Grenz-Linter fängt Importe nach oben ab. - Env über zwei Module mit Schema;
process.envaußerhalb durch Linter verboten;.env.examplevollständig. -
AppErrorundrequest()inshared/api, mit Tests abgedeckt (Netzwerk, Status, ungültiges Schema). - Provider in
_app; QueryClient nicht gemeinsam für Benutzer auf dem Server. - Design-Tokens in CSS-Variablen; alle verwendeten Klassen existieren.
- Vitest + MSW + Playwright eingerichtet, jeweils ein lebender Test.
- Git-Hooks und commitlint funktionieren.
- CI ist grün.
- Sicherheitsheader und
poweredByHeader: false. - Entscheidungen aus Abschnitt 1 dokumentiert.
12. Bei Zweifeln
- Zwischen „jetzt hinzufügen“ und „hinzufügen, wenn nötig“ – wenn nötig.
- Zwischen „manuell konfigurieren“ und „wie von create-next-app generiert lassen“ – lassen, solange es keinen Grund zur Änderung gibt, und den Grund dokumentieren, wenn geändert wird.
- Zwischen „eine Anwendung“ und „Monorepo“ – eine Anwendung.
- Zwischen „Konfiguration aus dem Gedächtnis“ und „Konfiguration aus der Dokumentation der aktuellen Version“ – aus der Dokumentation.