# Portfolio Project Instructions This is Dennis Konkol's personal portfolio (dk0.dev) - a Next.js 15 portfolio with Directus CMS integration, n8n automation, and a "liquid" design system. ## Build, Test, and Lint ### Development ```bash npm run dev # Full dev environment (Docker + Next.js) npm run dev:simple # Next.js only (no Docker dependencies) npm run dev:next # Plain Next.js dev server ``` ### Build & Deploy ```bash npm run build # Production build (standalone mode) npm run start # Start production server ``` ### Testing ```bash # Unit tests (Jest) npm run test # Run all unit tests npm run test:watch # Watch mode npm run test:coverage # With coverage report # E2E tests (Playwright) npm run test:e2e # Run all E2E tests npm run test:e2e:ui # Interactive UI mode npm run test:critical # Critical paths only npm run test:hydration # Hydration tests only ``` ### Linting ```bash npm run lint # Run ESLint npm run lint:fix # Auto-fix issues ``` ### Database (Prisma) ```bash npm run db:generate # Generate Prisma client npm run db:push # Push schema to database npm run db:studio # Open Prisma Studio npm run db:seed # Seed database ``` ## Architecture Overview ### Tech Stack - **Framework**: Next.js 15 (App Router), TypeScript 5.9 - **Styling**: Tailwind CSS 3.4 with custom `liquid-*` color tokens - **Theming**: next-themes for dark mode (system/light/dark) - **Animations**: Framer Motion 12 - **3D**: Three.js + React Three Fiber (shader gradient background) - **Database**: PostgreSQL via Prisma ORM - **Cache**: Redis (optional) - **CMS**: Directus (self-hosted, GraphQL, optional) - **Automation**: n8n webhooks (status, chat, hardcover, image generation) - **i18n**: next-intl (EN + DE) - **Monitoring**: Sentry - **Deployment**: Docker (standalone mode) + Nginx ### Key Directories ``` app/ [locale]/ # i18n routes (en, de) page.tsx # Homepage sections projects/ # Project listing + detail pages api/ # API routes book-reviews/ # Book reviews from Directus hobbies/ # Hobbies from Directus n8n/ # n8n webhook proxies projects/ # Projects (PostgreSQL + Directus) tech-stack/ # Tech stack from Directus components/ # React components lib/ directus.ts # Directus GraphQL client (no SDK) auth.ts # Auth + rate limiting translations-loader.ts # i18n loaders for server components prisma/ schema.prisma # Database schema messages/ en.json # English translations de.json # German translations ``` ### Data Source Fallback Chain The architecture prioritizes resilience with this fallback hierarchy: 1. **Directus CMS** (if `DIRECTUS_STATIC_TOKEN` configured) 2. **PostgreSQL** (for projects, analytics) 3. **JSON files** (`messages/*.json`) 4. **Hardcoded defaults** 5. **Display key itself** (last resort) **Critical**: The site never crashes if external services (Directus, PostgreSQL, n8n, Redis) are unavailable. All API routes return graceful fallbacks. ### CMS Integration (Directus) - GraphQL calls via `lib/directus.ts` (no Directus SDK) - Collections: `tech_stack_categories`, `tech_stack_items`, `hobbies`, `content_pages`, `projects`, `book_reviews` - Translations use Directus native system (M2O to `languages`) - Locale mapping: `en` → `en-US`, `de` → `de-DE` - API routes export `runtime='nodejs'`, `dynamic='force-dynamic'` and include a `source` field in JSON responses (`directus|fallback|error`) ### n8n Integration - Webhook base URL: `N8N_WEBHOOK_URL` env var - Auth via `N8N_SECRET_TOKEN` and/or `N8N_API_KEY` headers - All endpoints have rate limiting and 10s timeout protection - Hardcover reading data cached for 5 minutes ## Key Conventions ### i18n (Internationalization) - **Supported locales**: `en` (English), `de` (German) - **Primary source**: Static JSON files in `messages/en.json` and `messages/de.json` - **Optional override**: Directus CMS `messages` collection - **Server components**: Use `getHeroTranslations()`, `getNavTranslations()`, etc. from `lib/translations-loader.ts` - **Client components**: Use `useTranslations("key.path")` from next-intl - **Locale mapping**: Middleware defines `["en", "de"]` which must match `app/[locale]/layout.tsx` ### Component Patterns - **Client components**: Mark with `"use client"` for interactive/data-fetching parts - **Data loading**: Use `useEffect` for client-side fetching on mount - **Animations**: Framer Motion `variants` pattern with `staggerContainer` + `fadeInUp` - **Loading states**: Every async component needs a matching Skeleton component ### Design System ("Liquid Editorial Bento") - **Core palette**: Cream (`#fdfcf8`), Stone (`#0c0a09`), Emerald (`#10b981`) - **Custom colors**: Prefixed with `liquid-*` (sky, mint, lavender, pink, rose, peach, coral, teal, lime) - **Card style**: Gradient backgrounds (`bg-gradient-to-br from-liquid-*/15 via-liquid-*/10 to-liquid-*/15`) - **Glassmorphism**: Use `backdrop-blur-sm` with `border-2` and `rounded-xl` - **Typography**: Headlines uppercase, tracking-tighter, with accent point at end - **Layout**: Bento Grid for new features (no floating overlays) ### File Naming - **Components**: PascalCase in `app/components/` (e.g., `About.tsx`) - **API routes**: kebab-case directories in `app/api/` (e.g., `book-reviews/`) - **Lib utilities**: kebab-case in `lib/` (e.g., `email-obfuscate.ts`) ### Code Style - **Language**: Code in English, user-facing text via i18n - **TypeScript**: No `any` types - use interfaces from `lib/directus.ts` or `app/_ui/` - **Error handling**: All API calls must catch errors with fallbacks - **Error logging**: Only in development mode (`process.env.NODE_ENV === "development"`) - **Commit messages**: Conventional Commits (`feat:`, `fix:`, `chore:`) - **No emojis**: Unless explicitly requested ### Testing Notes - **Jest environment**: JSDOM with mocks for `window.matchMedia` and `IntersectionObserver` - **Playwright**: Uses plain Next.js dev server (no Docker) with `NODE_ENV=development` to avoid Edge runtime issues - **Transform**: ESM modules (react-markdown, remark-*, etc.) are transformed via `transformIgnorePatterns` - **After UI changes**: Run `npm run test` to verify no regressions ### Docker & Deployment - **Standalone mode**: `next.config.ts` uses `output: "standalone"` for optimized Docker builds - **Branches**: `dev` → staging, `production` → live - **CI/CD**: Gitea Actions (`.gitea/workflows/`) - **Verify Docker builds**: Always test Docker builds after changes to `next.config.ts` or dependencies ## Common Tasks ### Adding a CMS-managed section 1. Define GraphQL query + types in `lib/directus.ts` 2. Create API route in `app/api//route.ts` with `runtime='nodejs'` and `dynamic='force-dynamic'` 3. Create component in `app/components/.tsx` 4. Add i18n keys to `messages/en.json` and `messages/de.json` 5. Integrate into parent component ### Adding i18n strings 1. Add keys to both `messages/en.json` and `messages/de.json` 2. Use `useTranslations("key.path")` in client components 3. Use `getTranslations("key.path")` in server components ### Working with Directus - All queries go through `directusRequest()` in `lib/directus.ts` - Uses GraphQL endpoint (`/graphql`) with 2s timeout - Returns `null` on failure (graceful degradation) - Translations filtered by `languages_code.code` matching Directus locale ## Environment Variables ### Required for CMS ```bash DIRECTUS_URL=https://cms.dk0.dev DIRECTUS_STATIC_TOKEN=... ``` ### Required for n8n features ```bash N8N_WEBHOOK_URL=https://n8n.dk0.dev N8N_SECRET_TOKEN=... N8N_API_KEY=... ``` ### Database & Cache ```bash DATABASE_URL=postgresql://... REDIS_URL=redis://... ``` ### Optional ```bash SENTRY_DSN=... NEXT_PUBLIC_BASE_URL=https://dk0.dev ``` ## Documentation References - Operations guide: `docs/OPERATIONS.md` - Locale system: `docs/LOCALE_SYSTEM.md` - CMS guide: `docs/CMS_GUIDE.md` - Testing & deployment: `docs/TESTING_AND_DEPLOYMENT.md`