SEO - New Settings > SEO admin page (seo_settings in app_config): indexing switch, Google/Bing verification, X handle, JSON-LD identity (Person/Organization, sameAs), per-locale keywords, readiness checklist and open links for sitemap.xml / robots.txt / manifest. - robots.txt is now dynamic: disallows admin, api, success and coming-soon paths; blocks everything while indexing is off or maintenance is on. - sitemap.xml carries hreflang alternates per URL, lists only categories with published projects, and is empty while hidden. - Metadata: robots + verification meta, og:locale in de_DE/en_US/ar_AR form, alternateLocale, twitter site/creator, project cover as OG image with article type, noindex on /success and /coming-soon. - JSON-LD: WebSite + publisher graph on all public pages, CreativeWork per project (view-mode independent). Security - Maintenance bypass now requires a correctly signed admin cookie; the middleware previously only checked the cookie existed. Token helpers moved to lib/admin-session-token.ts (shared by proxy.ts and lib/admin-auth.ts). - Media uploads: magic-byte validation against the declared type, SVG sanitization (script/handlers/foreignObject/javascript: rejected), upload folder sanitized, kind inferred from the real file. - Media route: fixed prefix-based path check that accepted sibling directories, unknown extensions return 404, nosniff header, CSP sandbox on SVG, gif content type added. - External media URLs: protocol-relative (//host) URLs rejected. Portfolio - Project and category slugs share /portfolio/[slug]; saving now rejects a slug already used on the other side instead of silently shadowing it. Tooling/docs - Lint: ignore scripts/legacy-prisma-seed.cjs, drop unused import. - New docs/SEO.md; FEATURES, ARCHITECTURE (Drizzle instead of Prisma), admin spec and CLAUDE.md updated. - Tests for all of the above (unit + integration); suite green.
160 lines
8.8 KiB
Markdown
160 lines
8.8 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
# 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)
|
|
|
|
```bash
|
|
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 in `i18n/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 UI
|
|
- `components/admin/`, `components/dashboard/` — admin UI
|
|
- Server actions (`actions.ts` files in page directories) are the entry points for form submissions; they call `lib/*` 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`, `lib/media-storage.ts` |
|
|
| Contact flow | `lib/mail.ts` |
|
|
| SEO (metadata, robots, sitemap, JSON-LD) | `lib/metadata.ts`, `lib/seo-settings.ts`, `app/robots.ts`, `app/sitemap.ts` — see `docs/SEO.md` |
|
|
| Admin session token (middleware + auth) | `lib/admin-session-token.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.md` if structure is affected
|
|
- **Cross-cutting/architecture changes**: read `docs/ARCHITECTURE.md`, `docs/DOMAIN_RULES.md`, `docs/FEATURES.md`, and the relevant `specs/` file
|
|
- **SEO / metadata / robots / sitemap**: read `docs/SEO.md` first
|
|
|
|
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`); the `pre-push` hook (`.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 relevant `lib/app-config.ts` modules 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
|
|
```
|
|
|
|
- `VERB` is 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 `pre-push` test gate) live in `.githooks/` and are activated
|
|
per clone via `core.hooksPath`. You don't need to remember this: the `prepare`
|
|
npm script runs `git config core.hooksPath .githooks` automatically on every
|
|
`npm install`. The deploy server does not need it — it only pulls, never commits
|
|
or pushes, so no hook ever fires there. Emergency skip: `git commit --no-verify`. |