11 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
This is a Next.js 16 blog/portfolio application built with TypeScript, Tailwind CSS, and React 19. The project uses App Router with Static Site Generation (SSG) for blog posts stored as Markdown files.
Design Philosophy: Industrial/SCP-inspired aesthetic with terminal/cyberpunk elements. Sharp edges, thick borders, monospace fonts, darker color palettes (slate/zinc/cyan/emerald tones). No modern Material UI feel - think government documents, classified files, brutal utilitarian design.
Development Commands
# Development server (runs on port 3030)
npm run dev
# Production build
npm run build
# Start production server
npm run start
# Lint code
npm run lint
# Validate all markdown posts (frontmatter, format, tags)
npm run validate-posts
Architecture Overview
Next.js 16 App Router Structure
app/
├── @breadcrumbs/ # Parallel route for breadcrumb navigation
│ ├── default.tsx # Auto-generated breadcrumbs
│ ├── blog/[...slug]/ # Post-specific breadcrumbs with titles
│ ├── tags/[tag]/ # Tag-specific breadcrumbs
│ └── about/ # Static breadcrumbs
├── blog/
│ ├── page.tsx # Blog listing with all posts
│ └── [...slug]/ # Dynamic post routes (supports nested paths)
│ ├── page.tsx # Post rendering with SSG
│ └── not-found.tsx # Custom 404 for missing posts
├── about/page.tsx
├── layout.tsx # Root layout with metadata, fonts, breadcrumbs slot
└── page.tsx # Landing page (hero + featured posts)
Key Architectural Patterns:
- Parallel Routes:
@breadcrumbsslot renders dynamic navigation based on current route without prop drilling - Catch-all Routes:
[...slug]supports nested blog posts (e.g.,/blog/tech/article-name) - Static Generation:
generateStaticParams()pre-renders all blog posts at build time - Server Components by Default: All components are RSC unless marked with
'use client'
Markdown System
content/blog/ # Markdown files (supports nested directories)
├── example.md
└── tech/
└── article.md
lib/
├── markdown.ts # Core markdown utilities
│ ├── getPostBySlug() # Read single post with path sanitization
│ ├── getAllPosts() # Get all posts, sorted by date, recursive
│ ├── getRelatedPosts() # Find similar posts by tags
│ └── validateFrontmatter()
├── types/frontmatter.ts # TypeScript interfaces for Post, FrontMatter
└── utils.ts # formatDate(), formatRelativeDate(), generateExcerpt()
Frontmatter Schema:
---
title: string # Required
description: string # Required
date: "YYYY-MM-DD" # Required, ISO format
author: string # Required
tags: [string, string?, string?] # Max 3 tags
image?: string # Optional hero image
draft?: boolean # Exclude from listings if true
---
Security: Path sanitization prevents directory traversal attacks. All file reads use path.resolve() and validate paths stay within content/blog/.
Components Organization
components/
├── blog/
│ └── MarkdownRenderer.tsx # Client component for rendering markdown
│ # Custom components: images (Next Image),
│ # links (external vs internal), code blocks
├── layout/
│ ├── Breadcrumbs.tsx # Client component, uses usePathname()
│ └── BreadcrumbsSchema.tsx # Schema.org structured data for SEO
└── [future components]
Coding Standards for Next.js 16
File Naming Conventions
Files and Directories:
- Use kebab-case for all file names:
user-profile.tsx,blog-post.tsx - Special Next.js files:
page.tsx,layout.tsx,not-found.tsx,loading.tsx
Component Names (inside files):
- Use PascalCase:
export function UserProfile(),export default BlogPost
Variables, Functions, Props:
- Use camelCase:
const userSettings = {},function handleSubmit() {} - Hooks:
useTheme,useMarkdown
Constants:
- Use SCREAMING_SNAKE_CASE:
const API_BASE_URL = "..."
Why kebab-case for files?
- Cross-platform compatibility (Windows vs Unix)
- URL-friendly (file names often map to routes)
- Easier to parse and read
Theme Management & Reusability
Recommended Pattern: Use next-themes library for dark/light mode
// Root layout.tsx
import { ThemeProvider } from 'next-themes'
export default function RootLayout({ children }) {
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem={false}
storageKey="blog-theme"
>
{children}
</ThemeProvider>
</body>
</html>
)
}
Client Component for Toggle:
// components/theme-toggle.tsx
'use client'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
// Prevent hydration mismatch
useEffect(() => setMounted(true), [])
if (!mounted) return <div>...</div>
return <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle
</button>
}
Tailwind Configuration:
// tailwind.config.js
module.exports = {
darkMode: 'class', // Use 'class' strategy for next-themes
theme: {
extend: {
colors: {
// Define custom colors for consistency
'dark-primary': '#18181b',
'accent': { DEFAULT: '#164e63', hover: '#155e75' }
}
}
}
}
CSS Variables Pattern:
/* globals.css */
:root {
--bg-primary: 255 255 255;
--text-primary: 15 23 42;
}
.dark {
--bg-primary: 24 24 27;
--text-primary: 241 245 249;
}
/* Use in components */
.card {
@apply bg-[rgb(var(--bg-primary))] text-[rgb(var(--text-primary))];
}
Provider Pattern Best Practices
- Server Component Boundary: Keep
layout.tsxas Server Component, wrap onlychildrenwith Client Provider - Avoid Hydration Mismatches: Always use
suppressHydrationWarningon<html>tag - Client-Only Rendering: Use
useEffect+mountedstate for theme-dependent UI - Context Consumption: Only components using
useTheme()need'use client'directive - No Prop Drilling: Context makes theme accessible anywhere without passing props
Next.js 16 Specific Patterns
Async Server Components:
// app/blog/page.tsx
export default async function BlogPage() {
const posts = await getAllPosts() // Server-side data fetching
return <div>{posts.map(...)}</div>
}
Static Generation with Dynamic Routes:
// app/blog/[...slug]/page.tsx
export async function generateStaticParams() {
const posts = await getAllPosts()
return posts.map(post => ({ slug: post.slug.split('/') }))
}
export async function generateMetadata({ params }) {
const post = getPostBySlug(params.slug.join('/'))
return { title: post.frontmatter.title, ... }
}
Parallel Routes for Layout Composition:
// app/layout.tsx
export default function RootLayout({
children,
breadcrumbs, // From @breadcrumbs parallel route
}: {
children: React.ReactNode
breadcrumbs: React.ReactNode
}) {
return <>
{breadcrumbs}
{children}
</>
}
Project-Specific Patterns
Markdown Processing
- Always validate paths: Use
sanitizePath()to prevent directory traversal - Draft support: Posts with
draft: trueare excluded fromgetAllPosts() - Recursive directories: Blog posts can be organized in subdirectories (
content/blog/tech/post.md) - Reading time: Auto-calculated at 200 words/minute
- Date handling: Use Romanian locale (
ro-RO) for date formatting
SEO & Metadata
- Every page exports
metadata: Use Next.js 16'sMetadatatype - Dynamic metadata: Use
generateMetadata()for blog posts - Structured data: Include Schema.org
BreadcrumbListandBlogPosting - OpenGraph images: Reference
post.frontmatter.imagefor social sharing
Styling Guidelines
Color Palette:
- Backgrounds:
zinc-900,slate-900,slate-800 - Accents:
cyan-900,emerald-900,teal-900 - Text:
slate-100,slate-300,slate-500 - Borders:
border-2,border-4(thick, sharp)
Design Tokens:
- NO rounded corners: Use
rounded-noneor omit (default is sharp) - Monospace fonts: Apply
font-monofor terminal aesthetic - Uppercase labels: Use
uppercase tracking-widerfor headers - Border-heavy design: Thick borders (
border-4) over shadows - Classification labels: Add metadata like "FILE#001", "DOCUMENT LEVEL-1"
Typography:
- Primary font:
JetBrains Mono(monospace) - Headings:
font-mono font-bold uppercase - Body:
font-mono text-sm - Code blocks: Sharp borders, dark background, no syntax highlighting (for terminal feel)
Available Subagents
Use these specialized agents via /spec-implementation-and-review command:
nextjs-specialist- Next.js 15/16, App Router, SSG, API routesui-implementer- UI implementation with shadcn/ui, Tailwindui-css-specialist- CSS layouts, styling, responsive designreact-frontend-expert- React components, hooks, state managementnodejs-typescript-engineer- TypeScript, Node.js backend
Common Pitfalls
- Hydration Mismatches with Themes: Always use
suppressHydrationWarningon<html>and checkmountedstate before rendering theme-dependent UI - Image Paths: Use
/prefix for public assets (/blog/image.jpgnotblog/image.jpg) - Dynamic Routes: Remember to return
slugas array ingenerateStaticParams()for catch-all routes - Client Components: Minimize
'use client'usage - only add when using hooks, event handlers, or browser APIs - Path Security: Always use
sanitizePath()when reading markdown files - Date Formats: Use
YYYY-MM-DDin frontmatter, convert to Romanian locale for display - Port Configuration: Dev server runs on port 3030 (not default 3000)
Type Safety
- All utilities in
lib/are fully typed - Frontmatter structure enforced via
FrontMatterinterface - Use
Posttype for blog post objects - Avoid
any- useunknownif type is truly unknown, then narrow
Performance Notes
- SSG by default: All blog posts pre-rendered at build time
- Image optimization: Use Next.js
<Image>component - Font optimization: Google Fonts loaded via
next/font - No client-side data fetching: Markdown loaded server-side only
- Static exports: Pages are fully static HTML (no server required for hosting)