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>
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
- Project Overview
- Tech Stack
- Project Structure
- Development Workflow
- Pull Request Guidelines
- Content Workflows
- Code Style & Standards
- Testing
- Legacy Code & Migration
- Commit Guidelines
Getting Started
Prerequisites
- Node.js 18.x or higher
- npm 9.x or higher
- Git for version control
Installation
-
Clone the repository
git clone <repository-url> cd portfolio -
Install dependencies
npm install -
Set up environment variables
Create a
.env.localfile 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 -
Start the development server
npm run devThe 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 systemsrc/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 logicsrc/lib/- Utility libraries (Supabase client, helpers, etc.)src/messages/- Translation JSON files for each languagesrc/services/- API integrations and external service wrapperssrc/types/- TypeScript interfaces and type definitionssrc/utils/- Helper functions and utilitiespublic/- 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
-
Keep PRs Focused
- One feature or fix per PR
- Avoid mixing unrelated changes
- Break large features into smaller, reviewable PRs
-
Write Descriptive Commits
- Follow the Commit Guidelines
- Each commit should be a logical unit of work
- Avoid "WIP" or "Fix" commit messages
-
Update Documentation
- Update README.md if adding new features
- Update CONTRIBUTING.md if changing development workflow
- Add JSDoc comments for new public functions
-
Request Review
- Tag relevant reviewers
- Respond to review comments promptly
- Make requested changes in new commits (don't force push)
-
CI/CD Checks
- Ensure all automated checks pass
- Fix any linting or build errors
- Address test failures before requesting review
PR Review Process
- Automated Checks - All CI/CD checks must pass
- Code Review - At least one approval required
- Testing - Reviewer may test changes locally
- Merge - Squash and merge into main branch
- 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
-
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 -
Copy the template structure above into each file
-
Translate the content for each locale while keeping the same structure
-
Add project images (if any) to
public/images/portfolio/my-project/ -
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
- German:
-
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
Custom Components (MDX Feature)
You can embed React components:
This is a custom component in MDX! ```Step-by-Step: Adding a New Blog Post
-
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 -
Add frontmatter metadata with all required fields
-
Write the content using Markdown/MDX syntax
-
Add images to
public/images/blog/my-blog-post/ -
Generate blog images (optional) using the AI-powered script:
# Preview image generation npm run generate:images:dry # Generate actual images npm run generate:images -
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
- German:
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
-
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" } } } } -
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" } } } } -
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" } } } } -
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
- Maintain structure consistency - Use the same JSON structure across all locale files
- Use semantic keys - Keys should describe the content, not the translation (e.g.,
actions.submitnotactions.submitButton) - Organize by feature - Group related translations under feature namespaces
- Test all locales - Always verify translations in all three languages (de, en, sr)
- Avoid hardcoded text - All user-facing text should come from translation files
- 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:
- URL path segment (e.g.,
/de/,/en/,/sr/) - Browser language settings
- 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 optimizationsnext/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 lintbefore committing changes - Fix all linting errors - the project uses
--strictmode - 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 orunknown - Export types from a central
types/directory when shared
React Components
- Use functional components with hooks
- Follow the component structure:
- Imports
- Type definitions
- Component definition
- Helper functions (if needed)
- 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 fromtailwind-mergeto 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.tssuffix (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 fromnext-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.tsor.test.tsxextension 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
describeblocks to group related tests - Use
itortestfor 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
.tsfiles 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 tosrc/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 - Usesrc/components/instead - Don't reference
pages-vite/in new routes - Use App Router pages insrc/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:
- Discuss first - Open an issue or comment on existing migration issues
- Check migration plan - Ensure your work aligns with the overall strategy
- Migrate one component/page at a time - Small, focused PRs
- Test thoroughly - Ensure migrated code works identically
- Update imports - Fix all references to migrated code
- 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:
- Check the import path - Legacy code is in
-vite/or-old/directories - Look for usage - Search the codebase to see if it's imported anywhere
- Ask first - Open an issue or discussion if you're unsure
- When in doubt, don't touch it - Stick to the new architecture in
src/app/,src/components/, andsrc/messages/
Commit Guidelines
Commit Message Format
<type>: <subject>
<body>
Types
feat- New featurefix- Bug fixdocs- Documentation changesstyle- Code style changes (formatting, etc.)refactor- Code refactoringtest- Adding or updating testschore- 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
