styleguide

Next.js App Router (TypeScript) Style Guide

Table of Contents

  1. General Principles
  2. Folder and File Structure
  3. File Naming
  4. Imports
  5. React Components
  6. TypeScript
  7. Styling
  8. Testing
  9. Linting and Formatting
  10. Version Control

General Principles


Folder and File Structure

app/        # App Router pages
components/ # Reusable components
hooks/      # Custom hooks
lib/        # Utilities and helpers
services/   # API calls or external integrations
styles/     # Global and modular styles
types/      # TypeScript types and interfaces
context/    # Context API setup
middleware/ # Middleware (if needed)
config/     # Configuration files

File Naming

Examples:

app/home/page.tsx
components/ui/button.tsx
components/login-form.tsx
hooks/useFetch.ts
lib/formatDate.ts

Imports

// React or third-party libraries
import { useState } from 'react';
import { format } from 'date-fns';

// Internal components and helpers
import NavBar from '@/components/navbar';
import { formatDate } from '@/lib/formatDate';

React Components

Example:

interface LoginProps {
status?: string;
canResetPassword: boolean;
  
}

export default fanction LoginForm({status, canResetPassword}: LoginProps){

return (
  <div>LoginForm</div>

 )
}

TypeScript

{
  "compilerOptions": {
    "strict": true,
    "baseUrl": "src",
    "paths": {
      "@/*": ["*"]
    }
  }
}

Example:

// Correct
const fetchUser = async (id: number): Promise<User> => {
  // ...
};

// Incorrect
const fetchUser = async (id: number): Promise<any> => {
  // ...
};

Styling

Example:

import styles from './NavBar.module.css';

const NavBar: React.FC = () => {
  return <nav className={styles.nav}>NavBar</nav>;
};

export default NavBar;
import './globals.css';

export const metadata = {
  title: 'Next.js App',
  description: 'Generated by Next.js',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Testing

Example:

import { render, screen } from '@testing-library/react';
import Button from '@/components/Button';

test('renders Button with label', () => {
  render(<Button label="Click Me" onClick={() => {}} />);
  expect(screen.getByText(/Click Me/i)).toBeInTheDocument();
});

Linting and Formatting

module.exports = {
  root: true,
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true,
    },
  },
  extends: ['next/core-web-vitals', 'eslint:recommended', 'plugin:@typescript-eslint/recommended'],
  rules: {
    '@typescript-eslint/no-unused-vars': ['error'],
    'react/react-in-jsx-scope': 'off',
  },
};
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 80
}