Add a commit-msg git hook that rejects any subject not matching "VERB - Description" (ALL-CAPS verb + " - " + description), letting merge/revert/fixup commits through. Document the mandatory style and the no-attribution rule in CLAUDE.md, and note that .githooks is activated per clone with core.hooksPath. Prevents the prefix-less commits agents kept producing.
8.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
# Development
npm run dev # Start Next.js dev server
npm run build # Build for production (--webpack flag applied in package.json)
npm run lint # Run ESLint
npm run test # Run all tests with Vitest
npx vitest run tests/some-file.test.ts # Run a single test file
# Database (Drizzle ORM + drizzle-kit)
npm run db:generate # Generate a migration from schema changes
npm run db:migrate # Apply migrations
npm run db:push # Push schema to DB directly (dev shortcut, no migration)
npm run db:studio # Open Drizzle Studio to browse the database
Docker / task runner (see Makefile)
make start # Start DB container + dev server (http://localhost:3014)
make stop # Stop containers
make db-up # Start only the database container
make migrate # Apply Drizzle migrations
make studio # Open Drizzle Studio
make psql # Open a psql shell on the database
make test # Run the whole test suite + copy-paste summary
make deploy # (on server) Pull + rebuild + restart
make health # Hit /api/health via public URL
Architecture
This is a multilingual Next.js (App Router) portfolio site with an admin workspace. Stack: TypeScript, next-intl, Drizzle ORM + PostgreSQL, Tailwind CSS, Radix UI, framer-motion, nodemailer.
Routing overview
There are two applications sharing one Next.js instance:
Public site — app/[locale]/(site)/
Localized routes for de, en, ar. Default locale is dynamic (stored in AppConfig), read at request time via /api/site/default-locale. Maintenance mode redirects visitors to /coming-soon.
Admin workspace — app/_admin/ (canonical source)
Accessed via a dedicated subdomain (root.mohfarawati.de) in production, or via the /root path prefix in development. The middleware rewrites both to app/admin-internal/. The app/root/ and app/admin-internal/ directories mirror app/_admin/ — treat app/_admin/ as the source of truth.
The full routing rewrite logic lives in lib/admin-routing.ts and proxy.ts (the Next.js 16 middleware entry point — this project has no middleware.ts).
i18n
- Locales:
de,en,ar— defined ini18n/routing.ts - Default locale is configurable at runtime via
AppConfig(key:default_locale) localePrefix: "as-needed"— default locale has no prefix in URLs- No locale cookie or browser detection; locale is set explicitly by user
- Translation messages live in
messages/{locale}.json
Persistence
The Drizzle client is in lib/db/index.ts (postgres.js driver); the schema is in lib/db/schema.ts and DB enums in lib/db/enums.ts. All DB access must go through server-side modules in lib/. Client components must never access the database.
AppConfig is a key-value table used for all runtime configuration: site settings, SMTP, marquee, maintenance mode, default locale. lib/app-config.ts is the aggregate entry point; individual settings are in lib/site-settings.ts, lib/mail-settings.ts, lib/marquee-settings.ts.
Module boundaries
lib/*— server-side application logic (queries, services, config)components/ui/— shared Radix UI primitives (design system base)components/layout/,components/site/— public site UIcomponents/admin/,components/dashboard/— admin UI- Server actions (
actions.tsfiles in page directories) are the entry points for form submissions; they calllib/*modules - Business logic must not live inside UI components
Key canonical files
| Concern | File |
|---|---|
| i18n routing | i18n/routing.ts |
| Admin routing logic | lib/admin-routing.ts |
| Middleware (routing + auth) | proxy.ts |
| DB client (Drizzle) | lib/db/index.ts |
| DB schema | lib/db/schema.ts |
| AppConfig aggregate | lib/app-config.ts |
| Portfolio queries | lib/portfolio.ts |
| Media handling | lib/media.ts |
| Contact flow | lib/mail.ts |
Documentation to read by task scope
- Small UI/copy/style fixes: read only the relevant files
- Feature changes: read
specs/<feature>.md+docs/ARCHITECTURE.mdif structure is affected - Cross-cutting/architecture changes: read
docs/ARCHITECTURE.md,docs/DOMAIN_RULES.md,docs/FEATURES.md, and the relevantspecs/file
Update docs/ and specs/ only when the change affects feature scope, business rules, architecture, or public behavior.
Environment variables
Key variables (see .env.example for full list):
DATABASE_URL PostgreSQL connection string
NEXT_PUBLIC_SITE_URL Public site URL
NEXT_PUBLIC_ADMIN_URL Admin subdomain URL
ADMIN_HOST Admin hostname (used by middleware for host-based routing)
ADMIN_PASSWORD In-app admin session password
ADMIN_AUTH_SECRET JWT/cookie secret for admin session
ADMIN_BASIC_AUTH_USER HTTP Basic Auth user (optional, adds middleware-level protection)
ADMIN_BASIC_AUTH_PASS HTTP Basic Auth password
SITE_RUNTIME_ORIGIN Internal origin for middleware to fetch runtime state (defaults to http://127.0.0.1:3000 in production)
Working rules
- Tests are mandatory for every logic change, in the SAME change. New behaviour → new tests covering the intent (positive and negative cases), not one happy example. Deliberate change → update the affected tests and say which/why. A test that fails unexpectedly is a real bug → fix the code, not the test. Test the real thing — integration tests use a real (in-process PGlite) database, so do NOT mock our own
lib//DB layer; only true external boundaries (the admin session, third-party APIs, SMTP) may be substituted. Keep the suite green (make test); thepre-pushhook (.githooks/pre-push) enforces it. Frontend/UI is verified manually; add component tests only for components with real logic. - Before making any change, first explain the plan briefly and list the files that will be touched.
- Make the smallest safe change that solves the task.
- Do not modify unrelated files.
- Preserve existing architecture, naming, and folder conventions.
- Prefer server-side logic in
lib/*and keep business logic out of UI components. - Never access the database (Drizzle) from client components.
- For admin-related changes, treat
app/_admin/as the canonical source of truth unless explicitly told otherwise. - Do not add new dependencies unless absolutely necessary and explicitly justified.
- After code changes, run only the minimum relevant checks (for example: targeted test, lint on changed files, or build if necessary).
- If a task may affect routing, auth, i18n, or runtime config, inspect
proxy.ts,lib/admin-routing.ts,i18n/routing.ts, and the relevantlib/app-config.tsmodules first. - For schema or database changes, inspect the Drizzle schema (
lib/db/schema.ts), the drizzle-kit migration flow, and migration impact before editing. - Ask before performing large refactors, file moves, destructive changes, or broad formatting changes.
- When updating behavior, also update docs/specs if the change affects public behavior, business rules, or architecture.
Git commit messages (MANDATORY)
Every commit subject must follow this exact style, or the commit-msg hook
(.githooks/commit-msg) will reject the commit:
VERB - Short description
VERBis an ALL-CAPS verb, at least 3 letters — e.g.ADDED,FIXED,STYLED,REMOVED,RENAMED,REFACTORED,IMPROVED,DOCUMENTED,CLEANED,REVERTED,CONFIGURED,POLISHED.- Then exactly
" - "(space, hyphen, space). - Then a capitalized, imperative description with no trailing period.
Good: FIXED - Load .env in drizzle.config so drizzle-kit targets the right DB
Bad (rejected): Flatten portfolio category routes — no VERB prefix.
Bad (rejected): feat(hero): add backdrop — Conventional Commits is NOT used here.
Do not add any Co-Authored-By / "Generated with Claude Code" attribution
lines to commits in this repo. Write the body (when useful) as wrapped prose or
bullet points explaining the why, matching the existing history.
The hook and the test gate are activated per clone with:
git config core.hooksPath .githooks (emergency skip: git commit --no-verify).