Files
portfolio/CLAUDE.md
denshooter 60ea4e99be
All checks were successful
Gitea CI / test-build (push) Successful in 11m8s
chore: remove Sentry integration
Remove @sentry/nextjs and all related files since it was never actively used.
- Delete sentry.server.config.ts, sentry.edge.config.ts
- Delete sentry-example-page and sentry-example-api routes
- Clean up instrumentation.ts, global-error.tsx, middleware.ts
- Remove Sentry env vars from env.example and docs
- Update CLAUDE.md, copilot-instructions.md, PRODUCTION_READINESS.md

Middleware bundle reduced from 86KB to 34.8KB (-51KB).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-04 13:00:34 +01:00

5.8 KiB

CLAUDE.md - Portfolio Project Guide

Project Overview

Personal portfolio website for Dennis Konkol (dk0.dev). Built with Next.js 15 (App Router), TypeScript, Tailwind CSS, and Framer Motion. Uses a "liquid" design system with soft gradient colors and glassmorphism effects.

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 support (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, REST/GraphQL, optional)
  • Automation: n8n webhooks (status, chat, hardcover, image generation)
  • i18n: next-intl (EN + DE), message files in messages/
  • Monitoring: Console error logging (development mode only)
  • Deployment: Docker + Nginx, CI via Gitea Actions

Commands

npm run dev          # Full dev environment (Docker + Next.js)
npm run dev:simple   # Next.js only (no Docker)
npm run dev:next     # Plain Next.js dev server
npm run build        # Production build
npm run lint         # ESLint
npm run test         # Jest unit tests
npm run test:e2e     # Playwright E2E tests

Project Structure

app/
  [locale]/           # i18n routes (en, de)
    page.tsx           # Homepage (hero, about, projects, contact)
    projects/          # Project listing + detail pages
  api/                 # API routes
    book-reviews/      # Book reviews from Directus CMS
    content/           # CMS content pages
    hobbies/           # Hobbies from Directus
    n8n/               # n8n webhook proxies
      hardcover/       # Currently reading (Hardcover API via n8n)
      status/          # Activity status (coding, music, gaming)
      chat/            # AI chatbot
      generate-image/  # AI image generation
    projects/          # Projects API (PostgreSQL + Directus fallback)
    tech-stack/        # Tech stack from Directus
  components/          # React components
    About.tsx          # About section (tech stack, hobbies, books)
    CurrentlyReading.tsx  # Currently reading widget (n8n/Hardcover)
    ReadBooks.tsx      # Read books with ratings (Directus CMS)
    Projects.tsx       # Featured projects section
    Hero.tsx           # Hero section
    Contact.tsx        # Contact form
lib/
  directus.ts          # Directus GraphQL client (no SDK)
  auth.ts              # Auth utilities + rate limiting
prisma/
  schema.prisma        # Database schema
messages/
  en.json              # English translations
  de.json              # German translations
docs/                  # Documentation

Architecture Patterns

Data Source Hierarchy (Fallback Chain)

  1. Directus CMS (if configured via DIRECTUS_STATIC_TOKEN)
  2. PostgreSQL (for projects, analytics)
  3. JSON files (messages/*.json)
  4. Hardcoded defaults
  5. Display key itself as last resort

All external data sources fail gracefully - the site never crashes if Directus, PostgreSQL, n8n, or Redis are unavailable.

CMS Integration (Directus)

  • REST/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 translation system (M2O to languages)
  • Locale mapping: en -> en-US, de -> de-DE

n8n Integration

  • Webhook base URL: N8N_WEBHOOK_URL env var
  • Auth via N8N_SECRET_TOKEN and/or N8N_API_KEY headers
  • All n8n endpoints have rate limiting and timeout protection (10s)
  • Hardcover data cached for 5 minutes

Component Patterns

  • Client components with "use client" for interactive/data-fetching parts
  • useEffect for data loading on mount
  • useTranslations from next-intl for i18n
  • Framer Motion variants pattern with staggerContainer + fadeInUp
  • Gradient cards with liquid-* color tokens and backdrop-blur-sm

Design System

Custom Tailwind colors prefixed with liquid-:

  • liquid-sky, liquid-mint, liquid-lavender, liquid-pink
  • liquid-rose, liquid-peach, liquid-coral, liquid-teal, liquid-lime

Cards use gradient backgrounds (bg-gradient-to-br from-liquid-*/15 via-liquid-*/10 to-liquid-*/15) with border-2 and rounded-xl.

Key Environment Variables

# Required for CMS
DIRECTUS_URL=https://cms.dk0.dev
DIRECTUS_STATIC_TOKEN=...

# Required for n8n features
N8N_WEBHOOK_URL=https://n8n.dk0.dev
N8N_SECRET_TOKEN=...
N8N_API_KEY=...

# Database
DATABASE_URL=postgresql://...

# Optional
REDIS_URL=redis://...

Conventions

  • Language: Code in English, user-facing text via i18n (EN + DE)
  • Commit messages: Conventional Commits (feat:, fix:, chore:)
  • Components: PascalCase files in app/components/
  • API routes: kebab-case directories in app/api/
  • CMS data always has a static fallback - never rely solely on Directus
  • Error logging: Only in development mode (process.env.NODE_ENV === "development")
  • No emojis in code unless explicitly requested

Common Tasks

Adding a new CMS-managed section

  1. Define the GraphQL query + types in lib/directus.ts
  2. Create an API route in app/api/<name>/route.ts
  3. Create a component in app/components/<Name>.tsx
  4. Add i18n keys to messages/en.json and messages/de.json
  5. Integrate into the parent component (usually About.tsx)

Adding i18n strings

  1. Add keys to messages/en.json and messages/de.json
  2. Access via useTranslations("key.path") in client components
  3. Or getTranslations("key.path") in server components

Working with Directus collections

  • All queries go through directusRequest() in lib/directus.ts
  • Uses GraphQL endpoint (/graphql)
  • 2-second timeout, graceful null fallback
  • Translations filtered by languages_code.code matching Directus locale