Files
Portfolio/CONTRIBUTING.md
damjan_savicandClaude Sonnet 4.5 7fde675ee0 auto-claude: subtask-1-5 - Document branch/PR conventions and legacy code
Added comprehensive documentation for:
- Branch naming conventions with patterns and examples
- Pull request guidelines including pre-submission checklist, PR template, and best practices
- Legacy code explanation for components-vite, pages-vite, and locales-old directories
- Migration status and what to avoid touching during Vite to Next.js migration
- Updated table of contents with new sections

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-25 06:39:49 +01:00

38 KiB

Contributing to Portfolio

Thank you for your interest in contributing to this project! This guide will help you get started with development.

Table of Contents

Getting Started

Prerequisites

  • Node.js 18.x or higher
  • npm 9.x or higher
  • Git for version control

Installation

  1. Clone the repository

    git clone <repository-url>
    cd portfolio
    
  2. Install dependencies

    npm install
    
  3. Set up environment variables

    Create a .env.local file in the root directory with the required environment variables:

    # Supabase Configuration
    NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
    NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
    
    # Optional: OpenAI (for image generation scripts)
    OPENAI_API_KEY=your_openai_api_key
    
  4. Start the development server

    npm run dev
    

    The application will be available at http://localhost:3000

Available Scripts

Command Description
npm run dev Start development server with hot reload
npm run build Build production bundle
npm start Start production server (after build)
npm run lint Run ESLint with strict mode
npm test Run unit tests with Vitest
npm run test:coverage Run tests with coverage report
npm run build:images Optimize images using Sharp
npm run generate:images Generate blog images using OpenAI
npm run generate:images:dry Preview image generation without creating files

Project Overview

This is a personal portfolio website for Damjan Savić, showcasing work as an AI & Automation Specialist. The site features:

  • Multi-language support - German, English, and Serbian with automatic language detection
  • Progressive Web App - Installable and offline-capable
  • SEO optimized - Structured data (Schema.org), sitemap generation
  • Responsive Design - Mobile-first approach with Tailwind CSS
  • Blog System - MDX-based content with frontmatter support
  • Portfolio Gallery - Dynamic project showcase with filtering
  • Contact Form - Integrated with Supabase backend

Tech Stack

Core Framework

  • Next.js 15 - React framework with App Router
  • React 19 - UI library with latest features
  • TypeScript 5.5 - Type-safe development

Styling & UI

  • Tailwind CSS 3.4 - Utility-first CSS framework
  • Framer Motion 11 - Animation library
  • Lucide React - Icon library
  • Tailwind Merge - Utility for merging Tailwind classes
  • CVA (Class Variance Authority) - Component variants management

Content & Internationalization

  • MDX - Markdown + JSX for content pages
    • @mdx-js/loader, @mdx-js/mdx, @mdx-js/react
    • @next/mdx, next-mdx-remote
  • next-intl 3.26 - Internationalization (i18n)
  • gray-matter - Frontmatter parsing

Backend & Services

  • Supabase - Database, authentication, and backend services
    • @supabase/supabase-js
    • @supabase/ssr
  • OpenAI 4.77 - AI integration for content generation

Development Tools

  • Vitest - Unit testing framework
  • ESLint 9 - Code linting with Next.js config
  • PostCSS - CSS processing
  • Autoprefixer - CSS vendor prefixes
  • Sharp - Image optimization

Performance & Analytics

  • web-vitals - Performance metrics
  • react-intersection-observer - Lazy loading and scroll animations

Project Structure

portfolio/
├── public/                    # Static assets (images, fonts, etc.)
├── scripts/                   # Build and utility scripts
│   ├── optimize-images.js     # Image optimization
│   └── generate-blog-images.mjs # AI-powered image generation
├── src/
│   ├── app/                   # Next.js App Router
│   │   ├── [locale]/          # Internationalized routes
│   │   │   ├── about/         # About page
│   │   │   ├── blog/          # Blog listing & posts
│   │   │   ├── contact/       # Contact form
│   │   │   ├── dashboard/     # Admin dashboard
│   │   │   ├── imprint/       # Legal imprint
│   │   │   ├── leistungen/    # Services page
│   │   │   ├── login/         # Authentication
│   │   │   ├── portfolio/     # Portfolio gallery & projects
│   │   │   ├── privacy/       # Privacy policy
│   │   │   ├── terms/         # Terms of service
│   │   │   ├── layout.tsx     # Locale-specific layout
│   │   │   └── page.tsx       # Homepage
│   │   ├── globals.css        # Global styles
│   │   ├── layout.tsx         # Root layout
│   │   ├── not-found.tsx      # 404 page
│   │   ├── robots.ts          # Robots.txt generation
│   │   └── sitemap.ts         # Sitemap generation
│   ├── components/            # Reusable React components
│   ├── components-vite/       # Legacy Vite components
│   ├── data/                  # Static data and content
│   ├── hooks/                 # Custom React hooks
│   ├── i18n/                  # Internationalization config
│   ├── lib/                   # Utility libraries and helpers
│   ├── messages/              # Translation files (de, en, sr)
│   ├── middleware.ts          # Next.js middleware (i18n routing)
│   ├── pages-vite/            # Legacy Vite pages
│   ├── services/              # API and service integrations
│   ├── styles/                # Additional styles
│   ├── types/                 # TypeScript type definitions
│   └── utils/                 # Helper functions
├── .env.local                 # Environment variables (not in git)
├── .eslintrc.json             # ESLint configuration
├── docker-compose.yml         # Docker Compose setup
├── Dockerfile                 # Docker container config
├── next.config.ts             # Next.js configuration
├── package.json               # Dependencies and scripts
├── postcss.config.js          # PostCSS configuration
├── tailwind.config.js         # Tailwind CSS configuration
└── tsconfig.json              # TypeScript configuration

Key Directories Explained

  • src/app/ - Next.js App Router pages and layouts using the new file-based routing system
  • src/app/[locale]/ - All pages are nested under locale for multi-language support (de, en, sr)
  • src/components/ - Reusable UI components (buttons, cards, forms, navigation, etc.)
  • src/hooks/ - Custom React hooks for shared logic
  • src/lib/ - Utility libraries (Supabase client, helpers, etc.)
  • src/messages/ - Translation JSON files for each language
  • src/services/ - API integrations and external service wrappers
  • src/types/ - TypeScript interfaces and type definitions
  • src/utils/ - Helper functions and utilities
  • public/ - Static files served directly (images, fonts, favicons)
  • scripts/ - Build automation and utility scripts

Development Workflow

Branch Naming Conventions

Use descriptive branch names that follow these patterns:

Branch Type Pattern Example Description
Feature feature/<description> feature/add-dark-mode New features or enhancements
Bug Fix fix/<description> fix/mobile-nav-bug Bug fixes and error corrections
Documentation docs/<description> docs/update-readme Documentation updates
Refactoring refactor/<description> refactor/contact-form Code restructuring without behavior changes
Testing test/<description> test/add-unit-tests Adding or updating tests
Chore chore/<description> chore/update-dependencies Maintenance tasks, dependency updates

Guidelines:

  • Use lowercase with hyphens (kebab-case)
  • Keep branch names concise but descriptive (3-5 words max)
  • Avoid special characters except hyphens
  • Delete branches after they've been merged

Examples:

# ✅ Good
git checkout -b feature/cookie-consent-banner
git checkout -b fix/portfolio-filter-crash
git checkout -b docs/api-integration-guide

# ❌ Bad
git checkout -b myFeature
git checkout -b fix_bug
git checkout -b john-working-branch

1. Create a New Feature Branch

git checkout -b feature/your-feature-name

2. Make Your Changes

  • Follow the existing code patterns and structure
  • Use TypeScript for type safety
  • Write clean, readable code with appropriate comments
  • Test your changes locally

3. Test Your Changes

# Run development server
npm run dev

# Run linter
npm run lint

# Run tests
npm test

4. Build for Production

npm run build

Ensure the build completes without errors before submitting.

5. Submit a Pull Request

See the Pull Request Guidelines section below for detailed instructions.

Pull Request Guidelines

When you're ready to submit your changes, create a pull request (PR) following these guidelines to ensure a smooth review process.

Before Creating a PR

Pre-submission Checklist:

  • All tests pass (npm test)
  • Linter runs without errors (npm run lint)
  • Production build succeeds (npm run build)
  • Changes have been tested locally
  • Code follows project style guidelines
  • No debugging statements left in code (console.log, debugger, etc.)
  • Translation files updated for all locales (de, en, sr) if UI text changed
  • Documentation updated if necessary

PR Title Format

Use the same format as commit messages:

<type>: <brief description>

Examples:

feat: add cookie consent banner
fix: resolve mobile navigation menu bug
docs: update API integration guide
refactor: simplify contact form validation

PR Description Template

Provide a clear description of your changes:

## Summary
Brief overview of what this PR does and why.

## Changes Made
- Added new component for cookie consent
- Updated privacy policy page
- Added tests for consent logic

## Testing
- [ ] Tested on desktop browsers (Chrome, Firefox, Safari)
- [ ] Tested on mobile devices
- [ ] Verified all three languages (de, en, sr)
- [ ] Checked accessibility with screen reader

## Screenshots (if applicable)
[Add screenshots for UI changes]

## Related Issues
Closes #123
Related to #456

PR Best Practices

  1. Keep PRs Focused

    • One feature or fix per PR
    • Avoid mixing unrelated changes
    • Break large features into smaller, reviewable PRs
  2. Write Descriptive Commits

    • Follow the Commit Guidelines
    • Each commit should be a logical unit of work
    • Avoid "WIP" or "Fix" commit messages
  3. Update Documentation

    • Update README.md if adding new features
    • Update CONTRIBUTING.md if changing development workflow
    • Add JSDoc comments for new public functions
  4. Request Review

    • Tag relevant reviewers
    • Respond to review comments promptly
    • Make requested changes in new commits (don't force push)
  5. CI/CD Checks

    • Ensure all automated checks pass
    • Fix any linting or build errors
    • Address test failures before requesting review

PR Review Process

  1. Automated Checks - All CI/CD checks must pass
  2. Code Review - At least one approval required
  3. Testing - Reviewer may test changes locally
  4. Merge - Squash and merge into main branch
  5. Cleanup - Delete branch after merge

Handling Review Feedback

  • Be responsive - Address feedback within 1-2 days
  • Ask questions - If feedback is unclear, ask for clarification
  • Make changes - Commit review suggestions as new commits
  • Re-request review - After addressing all comments
  • Be respectful - Code reviews are about the code, not the person

What Gets Rejected

PRs will be rejected or require changes if:

  • Tests are failing
  • Linter errors exist
  • Build fails
  • Breaking changes without discussion
  • Missing translations for new UI text
  • No description or context provided
  • Touches legacy code without coordination (see Legacy Code)

Content Workflows

This section covers how to add and manage content for portfolio projects, blog posts, and translations. The project supports multi-language content in German (de), English (en), and Serbian (sr).

Adding Portfolio Projects

Portfolio projects are defined as TypeScript files with structured metadata and content. Each project must be created for all supported locales.

File Location Conventions

Portfolio project files follow this structure:

src/i18n/locales-old/
├── de/portfolio/projects/       # German portfolio projects
│   └── my-project.ts
├── en/portfolio/projects/       # English portfolio projects
│   └── my-project.ts
└── sr/portfolio/projects/       # Serbian portfolio projects
    └── my-project.ts

Important: Use the same filename (slug) across all locales to maintain consistency.

TypeScript Data Structure

Each portfolio project file must export an object with the following structure:

// src/i18n/locales-old/de/portfolio/projects/my-project.ts
export const myProject = {
  meta: {
    slug: 'my-project',                    // Unique identifier (same across locales)
    title: 'Project Title | Damjan Savić', // SEO-optimized title
    description: 'Brief project description for meta tags',
    excerpt: 'Short excerpt for project cards',
    date: '2025-01',                       // Publication date (YYYY-MM format)
    category: 'Category Name',             // e.g., "Web Development", "Data Processing"
    client: 'Client Name',                 // Client or organization
    duration: '2 Monate',                  // Project duration
    url: 'https://project-url.com',        // Live project URL (optional)
    repository: '',                        // GitHub repository URL (optional)
    documentation: '',                     // Documentation URL (optional)
    published: true,                       // Visibility flag
    featured: true,                        // Featured on homepage
    technologies: [                        // Technology stack
      'TypeScript',
      'React',
      'Next.js'
    ],
    tags: [                                // Project tags for filtering
      'Web Development',
      'Full Stack',
      'SEO'
    ]
  },
  content: {
    intro: 'Introduction paragraph describing the project overview.',

    challenge: {
      title: 'The Challenge',
      description: 'Brief description of the problem to solve.',
      points: [
        'Challenge point 1',
        'Challenge point 2',
        'Challenge point 3'
      ]
    },

    solution: {
      title: 'The Solution',
      description: 'How the problem was approached.',
      content: 'Detailed explanation of the solution.',
      points: [
        'Solution feature 1',
        'Solution feature 2',
        'Solution feature 3'
      ]
    },

    technical: {
      title: 'Technical Implementation',
      description: 'Technical approach and architecture.',
      points: [
        'Technical detail 1',
        'Technical detail 2',
        'Technical detail 3'
      ],
      code: `// Optional code snippet to showcase implementation
export function exampleFunction() {
  return 'Example code';
}`
    },

    implementation: {
      title: 'Implementation Process',
      description: 'How the project was built.',
      points: [
        'Implementation step 1',
        'Implementation step 2',
        'Implementation step 3'
      ]
    },

    results: {
      title: 'Results and Impact',
      description: 'Measurable outcomes and business impact.',
      points: [
        'Result metric 1',
        'Result metric 2',
        'Result metric 3'
      ]
    },

    conclusion: 'Final thoughts and project summary.'
  }
};

// Re-export for compatibility with the project import system
export default myProject;

Step-by-Step: Adding a New Portfolio Project

  1. Create the TypeScript file for each locale:

    # German version
    touch src/i18n/locales-old/de/portfolio/projects/my-project.ts
    
    # English version
    touch src/i18n/locales-old/en/portfolio/projects/my-project.ts
    
    # Serbian version
    touch src/i18n/locales-old/sr/portfolio/projects/my-project.ts
    
  2. Copy the template structure above into each file

  3. Translate the content for each locale while keeping the same structure

  4. Add project images (if any) to public/images/portfolio/my-project/

  5. Test the project by navigating to:

    • German: http://localhost:3000/de/portfolio/my-project
    • English: http://localhost:3000/en/portfolio/my-project
    • Serbian: http://localhost:3000/sr/portfolio/my-project
  6. Verify SEO metadata using browser DevTools to check meta tags

Adding Blog Posts

Blog posts use MDX (Markdown + JSX) format with frontmatter metadata. MDX allows you to embed React components directly in your content.

File Location Conventions

Blog posts are organized by locale:

src/content/blog/
├── de/                          # German blog posts
│   └── my-blog-post.mdx
├── en/                          # English blog posts
│   └── my-blog-post.mdx
└── sr/                          # Serbian blog posts
    └── my-blog-post.mdx

MDX File Structure

Each blog post must include frontmatter metadata at the top:

---
title: "Blog Post Title"
description: "Brief description for SEO and post previews"
date: "2025-01-20"
author: "Damjan Savić"
category: "Technology"
tags: ["React", "Next.js", "TypeScript"]
image: "/images/blog/my-blog-post/hero.jpg"
published: true
featured: false
excerpt: "A short excerpt that appears in post listings"
---

# Blog Post Title

Your content starts here. You can use standard Markdown syntax:

## Headings

Regular **bold** and *italic* text.

### Code Blocks

```typescript
// Code examples with syntax highlighting
const greeting = "Hello, World!";
console.log(greeting);

Lists

  • Bullet point 1
  • Bullet point 2
  • Bullet point 3

Images

Alt text

Custom Components (MDX Feature)

You can embed React components:

This is a custom component in MDX! ```

Step-by-Step: Adding a New Blog Post

  1. Create the MDX file for each locale:

    # German version
    touch src/content/blog/de/my-blog-post.mdx
    
    # English version
    touch src/content/blog/en/my-blog-post.mdx
    
    # Serbian version
    touch src/content/blog/sr/my-blog-post.mdx
    
  2. Add frontmatter metadata with all required fields

  3. Write the content using Markdown/MDX syntax

  4. Add images to public/images/blog/my-blog-post/

  5. Generate blog images (optional) using the AI-powered script:

    # Preview image generation
    npm run generate:images:dry
    
    # Generate actual images
    npm run generate:images
    
  6. Test the blog post by navigating to:

    • German: http://localhost:3000/de/blog/my-blog-post
    • English: http://localhost:3000/en/blog/my-blog-post
    • Serbian: http://localhost:3000/sr/blog/my-blog-post

Internationalization (i18n) Workflow

The project uses next-intl for internationalization with JSON-based translation files.

Translation File Structure

Translation files are located in src/messages/ and organized by locale:

src/messages/
├── de.json          # German translations
├── en.json          # English translations
└── sr.json          # Serbian translations

Translation File Format

Each translation file is a nested JSON object organized by page or section:

{
  "meta": {
    "site": {
      "name": "Damjan Savić",
      "title": "Damjan Savić | Fullstack Developer",
      "description": "Portfolio website description"
    }
  },
  "common": {
    "nav": {
      "home": "Home",
      "about": "About",
      "portfolio": "Portfolio",
      "blog": "Blog",
      "contact": "Contact"
    },
    "actions": {
      "readMore": "Read More",
      "viewMore": "View More",
      "back": "Back",
      "close": "Close"
    }
  },
  "pages": {
    "home": {
      "hero": {
        "title": "FULLSTACK DEVELOPER",
        "subtitle": "AI Agents | Voice AI | Process Automation"
      }
    }
  }
}

Step-by-Step: Adding Translations

  1. Add the translation key to src/messages/de.json (German - primary language):

    {
      "pages": {
        "contact": {
          "form": {
            "title": "Kontakt aufnehmen",
            "name": "Name",
            "email": "E-Mail",
            "message": "Nachricht",
            "submit": "Absenden"
          }
        }
      }
    }
    
  2. Add the same structure to src/messages/en.json (English):

    {
      "pages": {
        "contact": {
          "form": {
            "title": "Get in Touch",
            "name": "Name",
            "email": "Email",
            "message": "Message",
            "submit": "Submit"
          }
        }
      }
    }
    
  3. Add the same structure to src/messages/sr.json (Serbian):

    {
      "pages": {
        "contact": {
          "form": {
            "title": "Kontaktirajte nas",
            "name": "Ime",
            "email": "Email",
            "message": "Poruka",
            "submit": "Pošalji"
          }
        }
      }
    }
    
  4. Use the translation in your component:

    import { useTranslations } from 'next-intl';
    
    export function ContactForm() {
      const t = useTranslations('pages.contact.form');
    
      return (
        <form>
          <h2>{t('title')}</h2>
          <input placeholder={t('name')} />
          <input placeholder={t('email')} />
          <textarea placeholder={t('message')} />
          <button>{t('submit')}</button>
        </form>
      );
    }
    

Translation Best Practices

  1. Maintain structure consistency - Use the same JSON structure across all locale files
  2. Use semantic keys - Keys should describe the content, not the translation (e.g., actions.submit not actions.submitButton)
  3. Organize by feature - Group related translations under feature namespaces
  4. Test all locales - Always verify translations in all three languages (de, en, sr)
  5. Avoid hardcoded text - All user-facing text should come from translation files
  6. Keep translations concise - Match the tone and length of the original language when possible

Locale-Specific File Naming

When creating locale-specific content files, follow these conventions:

Content Type File Location Naming Pattern Example
Portfolio Projects src/i18n/locales-old/[locale]/portfolio/projects/ project-slug.ts ai-data-reader.ts
Blog Posts src/content/blog/[locale]/ post-slug.mdx react-best-practices.mdx
Translations src/messages/ [locale].json de.json
Page Content src/app/[locale]/[page]/ Standard Next.js routing page.tsx

Switching Between Locales

The application automatically detects the user's locale based on:

  1. URL path segment (e.g., /de/, /en/, /sr/)
  2. Browser language settings
  3. User preference stored in cookies

Users can manually switch languages using the language selector in the navigation menu.

Code Style & Standards

ESLint & Code Linting

This project uses ESLint to enforce code quality and consistency. The configuration extends Next.js recommended presets:

ESLint Configuration (.eslintrc.json):

{
  "extends": [
    "next/core-web-vitals",
    "next/typescript"
  ]
}

Key ESLint Rules:

  • next/core-web-vitals - Enforces Next.js best practices and Core Web Vitals optimizations
  • next/typescript - TypeScript-specific rules for Next.js applications

Running the Linter:

# Run ESLint in strict mode (recommended before committing)
npm run lint

# The linter will check for:
# - Code quality issues
# - Next.js best practices violations
# - TypeScript type errors
# - Unused variables and imports
# - Accessibility issues

Important:

  • Always run npm run lint before committing changes
  • Fix all linting errors - the project uses --strict mode
  • The linter runs automatically during the build process
  • ESLint errors will prevent successful production builds

Code Formatting

General Formatting Guidelines:

  • Indentation: 2 spaces (no tabs)
  • Line Length: Aim for 80-100 characters, hard limit at 120
  • Semicolons: Required at the end of statements
  • Quotes: Single quotes for strings (except in JSX/TSX where double quotes are preferred)
  • Trailing Commas: Use trailing commas in multi-line objects and arrays

Example:

// ✅ Good
const user = {
  name: 'John Doe',
  email: 'john@example.com',
  role: 'admin',
};

// ❌ Bad
const user = {
  name: "John Doe",
  email: "john@example.com",
  role: "admin"
}

TypeScript

  • Use TypeScript for all new files
  • Define proper types and interfaces
  • Avoid using any - use proper typing or unknown
  • Export types from a central types/ directory when shared

React Components

  • Use functional components with hooks
  • Follow the component structure:
    1. Imports
    2. Type definitions
    3. Component definition
    4. Helper functions (if needed)
    5. Exports

Example:

import { useState } from 'react';
import { Button } from '@/components/ui/Button';

interface MyComponentProps {
  title: string;
  onSubmit: () => void;
}

export function MyComponent({ title, onSubmit }: MyComponentProps) {
  const [isLoading, setIsLoading] = useState(false);

  const handleClick = async () => {
    setIsLoading(true);
    await onSubmit();
    setIsLoading(false);
  };

  return (
    <div>
      <h2>{title}</h2>
      <Button onClick={handleClick} disabled={isLoading}>
        Submit
      </Button>
    </div>
  );
}

Styling

  • Use Tailwind CSS utility classes
  • Use cn() utility from tailwind-merge to merge classes
  • Follow the mobile-first responsive design approach
  • Use CSS variables for theme colors (defined in globals.css)

File Naming

  • Components: PascalCase (e.g., MyComponent.tsx)
  • Utilities: camelCase (e.g., formatDate.ts)
  • Pages: lowercase (e.g., page.tsx, layout.tsx)
  • Types: PascalCase with .types.ts suffix (e.g., User.types.ts)

Import Organization

// 1. External dependencies
import { useState } from 'react';
import { useTranslations } from 'next-intl';

// 2. Internal components and utilities
import { Button } from '@/components/ui/Button';
import { formatDate } from '@/utils/date';

// 3. Types
import type { User } from '@/types/User.types';

// 4. Styles (if any)
import styles from './Component.module.css';

Internationalization

  • All user-facing text must be internationalized
  • Use useTranslations() hook from next-intl
  • Add translation keys to all language files in src/messages/

Example:

import { useTranslations } from 'next-intl';

export function WelcomeMessage() {
  const t = useTranslations('home');

  return <h1>{t('welcome')}</h1>;
}

Testing

This project uses Vitest as its testing framework. Vitest is a fast, modern test runner built for Vite-based projects, offering:

  • Lightning-fast execution with native ESM support
  • Jest-compatible API for easy migration and familiarity
  • First-class TypeScript support
  • Built-in coverage reporting with c8

Current State: The project is fully configured with Vitest, but no tests currently exist. We encourage contributors to add tests for new features and gradually improve test coverage.

Test Setup

Vitest is already configured in package.json:

{
  "scripts": {
    "test": "vitest",
    "test:coverage": "vitest run --coverage"
  },
  "devDependencies": {
    "vitest": "^1.3.1"
  }
}

Running Tests

# Run tests in watch mode (recommended during development)
npm test

# Run tests once (useful for CI/CD)
npm run test:coverage

# The test runner will:
# - Automatically detect .test.ts, .test.tsx, .spec.ts, .spec.tsx files
# - Re-run tests when files change (in watch mode)
# - Display coverage reports (with --coverage flag)

Writing Tests

When adding tests to this project, follow these conventions:

1. File Naming & Location

  • Place test files next to the code they test
  • Use .test.ts or .test.tsx extension for test files
  • Match the filename of the file being tested

Example:

src/utils/
├── formatDate.ts          # Source file
└── formatDate.test.ts     # Test file

2. Test Structure

  • Use describe blocks to group related tests
  • Use it or test for individual test cases
  • Write descriptive test names that explain the expected behavior

Example:

// src/utils/formatDate.test.ts
import { describe, it, expect } from 'vitest';
import { formatDate } from './formatDate';

describe('formatDate', () => {
  it('formats date in DD.MM.YYYY format', () => {
    const date = new Date('2024-01-15');
    expect(formatDate(date)).toBe('15.01.2024');
  });

  it('handles invalid dates gracefully', () => {
    const invalidDate = new Date('invalid');
    expect(formatDate(invalidDate)).toBe('Invalid Date');
  });
});

3. Component Testing

For React components, use Vitest with React Testing Library patterns:

// src/components/Button.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders with correct text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });
});

4. What to Test

  • Utility functions - Pure functions and helpers
  • Business logic - Complex calculations and transformations
  • Hooks - Custom React hooks with multiple states
  • Components - User interactions and rendering logic
  • API integrations - Service layer functions (use mocks)

5. Testing Conventions

  • Arrange-Act-Assert pattern for test structure
  • One assertion per test when possible (for clarity)
  • Mock external dependencies (API calls, database, etc.)
  • Test edge cases and error conditions
  • Use meaningful test data that reflects real usage

Example:

describe('calculateDiscount', () => {
  it('applies 10% discount for orders over $100', () => {
    // Arrange
    const orderTotal = 150;
    const discountThreshold = 100;

    // Act
    const result = calculateDiscount(orderTotal, discountThreshold);

    // Assert
    expect(result).toBe(135); // 150 - 15 (10%)
  });
});

Test Coverage Goals

While we don't enforce strict coverage requirements, aim for:

  • 80%+ coverage for utility functions and business logic
  • 60%+ coverage for components
  • 100% coverage for critical paths (authentication, payments, data validation)

Run npm run test:coverage to see current coverage reports.

Legacy Code & Migration

This project is currently undergoing a migration from Vite + React to Next.js 15. During this transition, you'll encounter legacy directories that contain the old Vite-based code. Understanding these directories is crucial to avoid conflicts and ensure a smooth migration process.

Legacy Directories

The following directories contain legacy code from the Vite implementation and should be handled with care:

Directory Type Description Status
src/components-vite/ Components Original Vite-based React components ⚠️ Legacy - Being migrated
src/pages-vite/ Pages Vite-based page components ⚠️ Legacy - Being migrated
src/i18n/locales-old/ Translations Old translation structure for portfolio projects ⚠️ Legacy - Partially used

What Are These Directories?

src/components-vite/

  • Purpose: Contains the original UI components built for the Vite + React architecture
  • Contents: Buttons, forms, navigation, cards, and other reusable components
  • Status: Being gradually replaced with Next.js-compatible components in src/components/
  • Why it exists: Serves as reference during migration and fallback for unmigrated features

src/pages-vite/

  • Purpose: Original Vite page components (before Next.js App Router)
  • Contents: Home, About, Portfolio, Blog, Contact pages and their subpages
  • Status: Being replaced with Next.js App Router pages in src/app/[locale]/
  • Why it exists: Historical reference and for any unmigrated route logic

src/i18n/locales-old/

  • Purpose: Legacy translation system for portfolio projects
  • Contents: TypeScript-based portfolio project definitions (see Adding Portfolio Projects)
  • Status: Still in active use for portfolio projects until migration to MDX is complete
  • Why it exists: Portfolio projects are currently defined as .ts files here, not yet moved to MDX

Migration Status

The migration from Vite to Next.js is actively ongoing. Here's the current state:

Completed:

  • Next.js 15 App Router setup
  • Internationalization with next-intl
  • Blog system migrated to MDX
  • Main layout and routing structure
  • New component architecture in src/components/

🚧 In Progress:

  • Migrating Vite components to Next.js components
  • Converting portfolio project definitions from TypeScript to MDX
  • Updating old translation structure
  • Removing dependency on legacy directories

Not Yet Started:

  • Complete removal of components-vite/
  • Complete removal of pages-vite/
  • Full MDX migration for portfolio projects

What to Avoid During Migration

To prevent conflicts, regression, and wasted effort, DO NOT:

Don't Modify Legacy Code

  • Don't edit files in src/components-vite/ - These components are being phased out
  • Don't edit files in src/pages-vite/ - Page structure has moved to src/app/[locale]/
  • Don't add new features to legacy directories - Build new features in the Next.js structure

Don't Create Dependencies on Legacy Code

  • Don't import from components-vite/ in new code - Use src/components/ instead
  • Don't reference pages-vite/ in new routes - Use App Router pages in src/app/
  • Don't expand the legacy translation structure - Add new content to src/messages/ or MDX files

Don't Delete Legacy Code Without Coordination

  • Don't delete legacy directories - They may still be referenced by unmigrated code
  • Don't remove legacy imports - Check if they're still in use first
  • Don't assume code is unused - Verify before removing

What You SHOULD Do

Use the New Architecture

  • Create new components in src/components/ - Follow Next.js patterns
  • Add new pages in src/app/[locale]/ - Use App Router file conventions
  • Add translations to src/messages/ - Use next-intl JSON format
  • Create blog posts as MDX - Follow the Adding Blog Posts guide

Reference Legacy Code for Patterns

  • Study components-vite/ for UI patterns - Understand the design system, then recreate in new structure
  • Check pages-vite/ for page logic - Learn how pages work, then build with App Router
  • Learn from locales-old/ structure - Understand content organization before migrating

Help with Migration (Advanced Contributors)

If you want to help migrate legacy code:

  1. Discuss first - Open an issue or comment on existing migration issues
  2. Check migration plan - Ensure your work aligns with the overall strategy
  3. Migrate one component/page at a time - Small, focused PRs
  4. Test thoroughly - Ensure migrated code works identically
  5. Update imports - Fix all references to migrated code
  6. Document changes - Note what was migrated and any breaking changes

Special Case: Portfolio Projects (locales-old/)

Current State: Portfolio projects are still using the legacy structure in src/i18n/locales-old/[locale]/portfolio/projects/.

Why: These TypeScript-based project definitions have not yet been migrated to MDX.

What this means for you:

  • You can still add portfolio projects using the TypeScript structure (see Adding Portfolio Projects)
  • ⚠️ This structure may change in the future when projects are migrated to MDX
  • 🔄 Follow existing patterns - Use the same structure as existing projects

Future Plan: Portfolio projects will eventually be migrated to MDX format (similar to blog posts) for consistency and easier content management.

Questions About Legacy Code?

If you're unsure whether code is legacy or active:

  1. Check the import path - Legacy code is in -vite/ or -old/ directories
  2. Look for usage - Search the codebase to see if it's imported anywhere
  3. Ask first - Open an issue or discussion if you're unsure
  4. When in doubt, don't touch it - Stick to the new architecture in src/app/, src/components/, and src/messages/

Commit Guidelines

Commit Message Format

<type>: <subject>

<body>

Types

  • feat - New feature
  • fix - Bug fix
  • docs - Documentation changes
  • style - Code style changes (formatting, etc.)
  • refactor - Code refactoring
  • test - Adding or updating tests
  • chore - Maintenance tasks

Examples

git commit -m "feat: add contact form validation"
git commit -m "fix: resolve mobile navigation menu bug"
git commit -m "docs: update README with deployment instructions"

Questions?

If you have any questions or need help, please:

  • Check existing documentation
  • Review similar code in the codebase
  • Open an issue for discussion

Built with Next.js + TypeScript + Tailwind CSS