Skip to content
Go back

Folder + index.ts: A Scalable Component Structure for Next.js

Modern Next.js apps grow fast — more pages, more components, more shared UI, more complexity. Without a clear component structure, imports become messy, files scatter across the repo, and onboarding new developers gets harder over time.

One simple pattern helps a lot: the Folder + index.ts component structure. This pattern keeps each component in its own directory and exposes a clean public API through a single index.ts file.

What is the Folder + index.ts pattern?

Instead of having a flat components directory with files like Footer.tsx, Header.tsx, and Button.tsx all at the same level, each component gets its own folder, plus an index.ts that re-exports what should be public.

components/
  layout/
    footer/
      Footer.tsx
      Footer.styles.ts
      Footer.test.tsx
      types.ts
      hooks.ts
      index.ts

With this setup, imports become:

// Before
import Footer from "@/components/layout/Footer";

// After
import Footer from "@/components/layout/footer";

When you import from a folder, bundlers like Vite, Webpack, or Next.js will automatically resolve the index.ts file in that folder, similar to how the web resolves index.html by default for a directory.

1. Clean, intuitive import paths

The first advantage is clean imports.

For example, you can rename Footer.tsx to FooterRoot.tsx and update only index.ts, while all consumers still import from @/components/layout/footer.

// components/layout/footer/index.ts
export { default as Footer } from "./FooterRoot";
export type { FooterProps } from "./FooterRoot";

This decouples how the component is implemented from how it is consumed.

2. Colocation of everything a component needs

The Folder + index.ts pattern encourages colocation: everything related to a component lives next to it. Typical files inside a component folder:

This improves discoverability:

3. Extensibility as components grow

As your app matures, a simple component often turns into a small ecosystem of sub-components, hooks, and utilities. The Folder + index.ts pattern scales with that growth. You might start with:

// index.ts
export { default as Footer } from "./Footer";

Later, you add more:

// index.ts
export { default as Footer } from "./Footer";
export { FooterSocial } from "./FooterSocial";
export { FooterNewsletter } from "./FooterNewsletter";
export * from "./types";

Consumers still import from the same folder:

import { Footer, FooterSocial } from "@/components/layout/footer";

You gain:

4. Barrel exports and public API control

The index.ts file is essentially a barrel file: it re-exports selected modules from within the folder. This lets you define a clean public API and hide implementation details. Example:

// components/layout/footer/index.ts
export { default as Footer } from "./Footer";
export type { FooterProps } from "./Footer";
export { FooterSocial } from "./FooterSocial";

// Not exported: internal utilities, test helpers, etc.

Benefits:

Barrel files can create circular dependencies if abused, so keep them scoped and focused at the component level and avoid giant, project-wide barrels.

5. Better tree shaking and bundle health

Properly structured exports make it easier for bundlers to apply tree shaking, removing unused code paths from the final bundle.

When each folder exposes:

…bundlers can include only what is actually imported. Combine this with good bundler configuration (like sideEffects: false for purely functional modules) to get smaller bundles and faster load times.

Example:

// package.json (for libraries or shared packages)
{
  "sideEffects": false
}

6. Future-proofing your Next.js architecture

Next.js promotes co-located layouts, components, and route segments in the app directory, and a Folder + index.ts structure fits nicely with that philosophy.

As features evolve:

All of this makes refactoring safer because external code depends only on the folder-level API, not on individual inner file names.

7. Consistency and onboarding

File structure is partly preference, but consistency is what really matters. Many React and Next.js codebases use some variation of:

When the pattern is applied across the project:

8. TypeScript: exporting types from index.ts

In a TypeScript-based Next.js app, the Folder + index.ts pattern also improves type safety ergonomics.

Instead of importing types from deep paths, you can re-export them from the component’s index.ts:

// index.ts
export { default as Footer } from "./Footer";
export type { FooterProps } from "./Footer";

Consumers get a cleaner API:

import { Footer } from "@/components/layout/footer";
import type { FooterProps } from "@/components/layout/footer";

This keeps types aligned with their component and makes refactors less error-prone because you centralize the types that are meant to be public.

Practical guidelines for adopting this pattern

When introducing the Folder + index.ts structure in an existing Next.js project, these steps help:

  1. Start with new components

    Use the pattern for any new component first, so the codebase gradually moves toward the new structure.

  2. Refactor high-traffic components

    Move frequently used components (like layout pieces and shared UI) into folders with index.ts to maximize DX gains early.

  3. Keep barrels focused

    Create index.ts per component folder, but avoid large root-level barrels that export your entire project. This can lead to circular dependencies and make refactoring more complex.

  4. Document the convention

    Add a short “Component structure” section to your project’s README or contributing guide so all team members follow the same pattern.

  5. Watch for circular dependencies

    Be careful not to import the barrel from files it re-exports; prefer relative imports inside the folder and barrel imports outside it.

Here is a minimal example tying it all together:

// components/layout/footer/Footer.tsx
import type { FooterProps } from "./types";
import { useFooterLinks } from "./hooks";

const Footer = ({ showSocial }: FooterProps) => {
  const links = useFooterLinks();
  // render JSX...
};

export default Footer;
// components/layout/footer/types.ts
export type FooterProps = {
  showSocial?: boolean;
};
// components/layout/footer/hooks.ts
export const useFooterLinks = () => {
  // custom logic...
  return [];
};
// components/layout/footer/index.ts
export { default as Footer } from "./Footer";
export type { FooterProps } from "./types";

Usage:

import { Footer } from "@/components/layout/footer";

This small abstraction buys a lot: clean imports, clear boundaries, and a path that scales as your app and team grow.

If your Next.js project is starting to feel hard to navigate, adopting the Folder + index.ts pattern is a low-risk, high-impact improvement to your architecture and developer experience.


Share this post on:

Previous Post
Mastering the JavaScript Event Loop: A Practical Guide for Beginners