Next.js and TypeScript Component Organization 101: Best Practices

How to organize components in a Next.js project using TypeScript. A well-organized project structure helps in maintaining and scaling the application as it grows.

July 30, 2024

By Kat Lea

Best Practices in Component Organization for Next.js and TypeScript

Organizing your components in a Next.js project using TypeScript can significantly improve your codebase's maintainability and scalability. This blog will walk you through best practices for structuring your components, illustrated with code snippets and examples.

Component Types and Folder Organization

Components in a Next.js can be categorized into three types: shared components, layout components, and specific components.

Shared components are reusable elements, like buttons or form inputs, used across various parts of the application.

Layout components define the structure of the application's pages, such as headers, footers, and navigation bars, ensuring a consistent look and feel throughout the site.

Specific components are tailored to particular features or pages, encapsulating functionality unique to those areas, like user profile cards or specialized content displays.

This categorization helps maintain a clean and organized codebase, making maintenance and scalability easier.

Below are some code examples of each component type:

Shared Components:

These are reusable components that can be used across various parts of your application.

// /components/Shared/Button.tsx
import React from 'react';

type ButtonProps = {
  text: string;
  onClick: () => void;
};

export const Button = ({ text, onClick }: ButtonProps) => {
  return (
      <button 
          className='bg-gray-800 text-white px-4 py-3 rounded hover:bg-gray-600' 
          onClick={onClick}>{text}</button>
  );
};

Layout Components:

These components are used to structure the layout of your pages.

// /components/Layout/Header.tsx
import React from 'react';

type HeaderProps = {
    title: string;
    links: { 
        href: string; 
        label: string 
    }[]; 
}

export const Header = ({ title, links }: HeaderProps) => {
  return (
    <header className="bg-gray-900 w-full h-20 p-6 flex flex-row gap-20">
        <h2 className="text-white heading-2">{title}</h2>

    {links && (
      <nav>
        <ul className="flex flex-row gap-6">
          {links.map((link, index) => (
            <li key={index}>
              <a className="text-white body-medium" href={link.href}>{link.label}</a>
            </li>
          ))}
        </ul>
      </nav>
    )}
  </header>
  );
};

Specific Components:

Components specific to a particular page or feature can be placed in dedicated folders within the /components directory.

// /components/Profile/ProfileCard.tsx
import React from 'react';

type ProfileCardProps = {
  name: string;
  age: number;
  bio: string;
  website?: string;
};

export const ProfileCard = ({ name, age, bio, website }: ProfileCardProps) => {
  return (
    <div className="flex h-fit w-80 flex-col items-center gap-4 rounded-lg bg-gray-200 p-10 drop-shadow-md">
      <h4 className="heading-4 text-black">{name}</h4>
      <p className="body-regular text-black">{age}</p>
      <p className="body-regular text-black">{bio}</p>
      {website && (
        <a className="body-small text-blue-800 hover:text-blue-600" href={website}>
          Website
        </a>
      )}
    </div>
  );
};

Naming Conventions

Consistent naming conventions are crucial for enhancing code readability and maintainability in a Next.js project using TypeScript.

For component names, use PascalCase (e.g., SubmitButton or UserProfile) to distinguish them from functions or variables.

Name props and methods using camelCase (e.g., onSubmit, userName) to align with JavaScript's standard practice, making the code intuitive for developers familiar with the language.

For custom style classes, use snake_case (e.g., button_label, profile_card) to differentiate these identifiers and ensure styles are easily identifiable and manageable within the codebase.

This systematic naming approach helps quickly locate files and understand the structure and purpose of each code component, improving overall productivity and collaboration within the development team.

// /components/Shared/SubmitButton.tsx
import React from 'react';
import styles from './submit-button.module.scss';

type SubmitButtonProps = {
  label: string;
  onSubmit: () => void; //using camel case for props
};

//using pascal case for the component name
export const SubmitButton = ({ label, onSubmit }: SubmitButtonProps) => {
  return (
      //using snake case for the custom style class
      <button onClick={onSubmit} className={styles.button_label}>{label}</button>
     );
};

export default SubmitButton;

Organizing Code in a Component

Properly organizing code within a component is essential for readability and maintainability. A well-structured component typically follows a consistent order:

  1. Imports: Bring in necessary dependencies.
  2. Type definitions (Props): Define props and state to clarify the data the component expects and manages.
  3. Component definition: Outline the core logic and rendering behavior.
  4. Hooks: Group hooks like useState and useEffect together at the top to handle state and lifecycle events.
  5. Event handlers and methods: Organize these in a section to easily find and modify interaction logic.
  6. Render logic (JSX): Ensure it is clear and concise, encapsulating the component's UI structure.

This organized approach makes the code easier to read and understand, simplifies debugging, and facilitates future modifications, as developers can quickly locate and update specific parts of the component.

// /components/Profile/ProfileCard.tsx

// 1. Imports
import React, { useEffect, useState } from 'react';

// 2. Type Definitions (Props)
type ProfileCardProps = {
  name: string;
  age: number;
  bio: string;
};

// 3. Component Definition
export const ProfileCard = ({ name, age, bio }: ProfileCardProps) => {
  // 4. Hooks
  const [isContentVisible, setIsContentVisible] = useState(false);

  useEffect(() => {
    console.log('Profile Card component mounted');
    return () => {
      console.log('Profile Card component unmounted');
    };
  }, []);

  // 5. Event Handlers and Methods
  const toggleContentVisibility = () => {
    setIsContentVisible(!isContentVisible);
  };

  // 6. Render Logic (JSX)
  return (
    <div className="flex h-fit w-80 flex-col items-center gap-4 rounded-lg bg-gray-200 p-10 drop-shadow-md">
      <h4 className="heading-4 text-black">{name}</h4>
      <p className="body-regular text-black">{age}</p>
      <p className="body-regular text-black">{bio}</p>(
      <button
        onClick={toggleContentVisibility}
        className="body-small rounded-md bg-blue-200 px-4 py-2 text-blue-800 transition-colors hover:bg-blue-100 hover:text-blue-600"
      >
        Website
      </button>
    </div>
  );
};

Using TypeScript With Next.js

TypeScript enhances development by providing type safety, helping to catch errors early, and offering better tooling to improve code quality and productivity. To set up TypeScript in your Next.js project, start by adding a tsconfig.json file. This file allows you to configure various compiler options and settings. This setup ensures your project benefits from TypeScript's robust features, making your code more reliable and maintainable.

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "strictFunctionTypes": false,
    "downlevelIteration": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "allowSyntheticDefaultImports": true,
    "noImplicitReturns": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "incremental": true,
    "forceConsistentCasingInFileNames": false
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

Organize It

In this guide, we've explored best practices for organizing components in a Next.js project using TypeScript. By categorizing components into shared, layout, and specific types, we can create a more maintainable and scalable project structure. Consistent naming conventions and clear organization within component files enhance readability and ease of maintenance.

Additionally, TypeScript's type safety and tooling benefits make it invaluable to a Next.js project. It helps catch errors early and ensures that the codebase remains robust as the project grows.

By following these best practices, developers can build a clean and well-structured Next.js application that is easy to navigate and extend. This leads to more efficient and enjoyable development experiences.



Kat Lea

Kat Lea

Front-End Developer

Kat is a front-end developer with a diploma in Contemporary Web Design. She has experience working in JavaScript based frameworks and libraries. Outside of work, she enjoys music, gaming with friends, and nature photography.