KeynouProgramming
Articles
Sign InGet Started
© 2026 Programming Keynou. All rights reserved.
Privacy PolicyTerms of ServiceContact
Back to Articles

Best Practices for File Structure in NextJS Projects

12/9/2025
Python Programming
Next.jsDockerCloud Deployments

Best Practices for File Structure in Next.js Projects: A Deep Dive for Startup Founders

Arriving at the “right” file structure is one of the most pivotal—yet frequently underestimated—decisions when starting a Next.js project. For startup founders in Python-driven organizations, understanding this seemingly frontend-focused topic can pay dividends, especially as your product scales, more engineers onboard, and deployments shift toward cloud and Docker-based infrastructure.

This article isn’t just about creating tidy folders. It’s a step-by-step, technical guide to designing, refactoring, and scaling Next.js applications so that future contributors, cloud deployments, and Docker builds all benefit. Whether you’re a seasoned Python backend engineer new to JavaScript frameworks, or a fullstack founder, you’ll walk away with practical, production-tested insights.

What is a File Structure in Next.js?

A “file structure,” also called a directory structure or folder organization, is simply the way files and folders are arranged within your application’s root directory. In Next.js, file structure is not just for aesthetics. The framework heavily leverages convention over configuration, which means it expects code and assets to live in particular folders.

Next.js supports a file-based routing system—meaning your directory tree literally defines your frontend URLs. This is tightly coupled to how Next.js bundles, optimizes, and (in Server Components mode) streams your UI.

Understanding—and optimizing—this structure is critical for maintainability, developer onboarding, CI/CD, Docker image caching, and even SEO.

Default Next.js Project Structure Explained

Let’s break down the typical scaffolding produced by npx create-next-app@latest:


my-nextjs-app/
├── node_modules/
├── pages/ (or app/)
├── public/
├── styles/
├── package.json
├── next.config.js
├── .env
└── README.md

Here’s what each piece means:

  • node_modules/ — All NPM-installed packages, not directly managed by humans.
  • pages/ (or app/) — The heart of the routing system. Every JS/TS file becomes a route. (More on file-based routing below.)
  • public/ — Static files—images, fonts—served as-is at root URL.
  • styles/ — CSS or other styling approaches (Sass/Less). Modern Next.js often uses CSS Modules or styled-components—this ‘styles’ folder is optional.
  • package.json — Project manifest: metadata, dependency declarations, scripts for build, test, lint, etc.
  • next.config.js — Central configuration for customizing framework behavior.
  • .env — Secret configuration loaded into process.env for use in builds and runtime.
  • README.md — Self-documentation for the project.

What is File-Based Routing in Next.js?

File-based routing means the page directory structure directly maps to site URLs. For example, a file at pages/blog/hello.js generates a route at /blog/hello.

In Next.js 13+ (with the app/ directory), routing is more powerful and supports layouts and server/client components. However, the core idea is the same: folders and files define your navigation tree.


app/
├── layout.js     // Top-level layout (e.g., header/footer)
├── page.js       // Index or root route
├── blog/
│   ├── layout.js // Blog section layout
│   ├── page.js   // /blog route
│   ├── [slug]/
│   │   └── page.js // Dynamic route, e.g., /blog/hello-world

This is declarative routing: the directory tree self-documents your site nav.

Why is this important for founders? As a codebase grows, team members waste time hunting for features if routing is muddled. Clear structure reduces onboarding friction and bugs.

Best Practices for Organizing Next.js Projects

1. Feature-based or Domain-Driven Folders

Rather than shoving all pages into a flat pages/ or app/ folder, organize by feature (user/, dashboard/, blog/), or by domain—especially for SaaS startups with several product modules. This is called modularization.

Technical term: “Domain-driven design” (DDD) means grouping code—UI, logic, tests—by business context, not file type (view, style, util, etc.).


app/
├── dashboard/
│   ├── layout.js
│   ├── page.js    // /dashboard
│   └── [id]/      // dynamic segments (e.g., /dashboard/123)
│       └── page.js
├── user/
│   ├── layout.js
│   ├── page.js
│   └── profile/
│       └── page.js

Advantages:

  • Easier to assign ownership: Teams can own domains or features.
  • Good fit for monorepos or Docker-based microservices.
  • Helps when code is used across multiple cloud deployments.

2. Keep Reusable Components Out of Routes

If you create a Button component used in several places, don’t put it inside any specific app/feature/ folder. Instead, have a top-level components/ directory for global, reusable UI patterns.

For clarity, “directory components” (re-usable folders per feature) can live within feature folders, while global components reside at the root.


components/
├── Button.jsx
├── Modal.jsx

This fosters reusability and makes new contributors immediately aware of your UI toolkit.

3. Utility and Hooks Separation

Next.js enables extensive code splitting, so utility functions (“helpers”), and custom React hooks (functions that begin with use*, which manage state or side effects) should not bloat your main app folders. Place:

  • hooks/: For useAuth.js, useWindowSize.js, etc.
  • utils/: For general-purpose logic like formatCurrency.js, slugify.js, etc.

4. Public Static Assets

Images, fonts, and other static files needed at runtime should live in public/. Refer to them via /logo.png, not via import unless using dynamic <Image /> components (which can optimize backgrounds or critical assets).

5. Co-locate Tests

Place relevant tests (*.test.js or *.spec.ts) alongside the feature/component code. This helps developers update logic and tests together, helpful for rapid iteration in a startup context.

6. Environment Configs and Secrets

All apps need configuration. Next.js supports .env.local, .env.development, and .env.production. Never hard-code secrets like API keys—use environment variables that Docker and Cloud Deployments can inject at build time or runtime.


.env.local              # Developer machines (not committed)
.env.development        # Development cloud deployments
.env.production         # For prod Docker builds / cloud

A solid structure makes CI/CD pipelines, Dockerfiles, and cloud platform integrations more secure and reliable.

Sample File Structure: A Real-World SaaS Startup Example

Let’s walk through a common layout for a production-scale SaaS built in Next.js, with Docker and multiple cloud environments.

Suppose your SaaS offers a workspace dashboard, blog, authentication, and settings.

Imagine the directory visually as follows:


app/
├── dashboard/
│   ├── layout.js
│   └── [workspaceId]/
│       └── page.js
├── blog/
│   ├── page.js
│   └── [slug]/
│       └── page.js
├── auth/
│   ├── layout.js
│   ├── login/
│   │   └── page.js
│   └── signup/
│       └── page.js
├── settings/
│   └── page.js
├── layout.js
├── page.js

components/               # Global UI widgets
├── Header.jsx
├── Sidebar.jsx
├── Loader.jsx

hooks/                    # Shared React hooks
├── useAuth.js
├── useUser.js

utils/                    # Pure JavaScript logic utilities
├── api.js
├── formatDate.js

public/                   # Externally-accessible, non-bundle files
├── logo.svg
├── favicon.ico

styles/                   # Global CSS, can use CSS Modules per component too
├── globals.css

tests/                    # End-to-end or integration test setups (optional)

.env
.env.development
.env.production
next.config.js
Dockerfile
README.md

How does this structure help with Docker and Cloud Deployments?

  • Docker builds can COPY only the necessary root folders for production images—no dev junk.
  • Cloud deployment actions (GitHub Actions, Vercel, etc.) can inject the correct .env.* file for the target environment.
  • Isolates “build step” files needed for Node.js server versus files (e.g., documentation, tests) that can be skipped for lean deploys.

Technical Deep Dive: Scaling, Performance, and Trade-offs

Why Not Flat Structure?

Early prototypes often use a flat directory with all pages in pages/ or app/, and all components in components/. While simple, this rapidly breaks down:

  • You get thousands of files—navigation and ownership are unclear.
  • Refactoring becomes high-risk—a single route change can cause errors in unrelated areas.
  • Testing, linting, and hot-reloading slow down—especially in local Docker builds.

What About Monorepos?

As your engineering team grows, consider monorepo tools (like pnpm workspaces or TurboRepo) that segment Next.js (frontend) and Python (API, worker, analytics) code in parallel, under a unified versioning and CI/CD pipeline. Good file structure at the project root level helps coordinate cloud and Docker deployments efficiently.

Performance and Docker Image Size

Keeping the public/ folder lean (only required static assets), and separating dev and prod dependencies in package.json, allows you to:

  • Minimize Docker image size (smaller attack surface, faster CI/CD builds).
  • Share persistent build cache layers (for node_modules, Next.js/.next, etc.)—critical for rapid multi-cloud deploys.

Example: Dockerfile for Optimized Next.js Deployment


# 1. Use official Node.js image
FROM node:18-alpine as builder
WORKDIR /app

# 2. Copy only package manifests (leverages Docker cache)
COPY package*.json ./

# 3. Install production dependencies
RUN npm ci --only=production

# 4. Copy source code
COPY . .

# 5. Build Next.js app (outputs to .next folder)
RUN npm run build

# 6. Create lightweight runtime image
FROM node:18-alpine
WORKDIR /app

COPY --from=builder /app/.next .next
COPY --from=builder /app/public public
COPY --from=builder /app/package.json .
COPY --from=builder /app/node_modules node_modules

ENV NODE_ENV=production

CMD ["npm", "start"]

This multi-stage Dockerfile leverages directory structure to optimize build caching, reduce image size, and cleanly separate dependencies for production cloud deployments.

Practical Example: Adding a New Feature

You want to add a “notifications” panel for your users.

Step 1: Create a notifications/ feature folder in app/:


app/
├── notifications/
│   ├── page.js
│   ├── layout.js

Step 2: Put the reusable UI (bell icon, dropdown menu) into components/NotificationBell.jsx.

Step 3: Fetch data using a dedicated hook, hooks/useNotifications.js


export function useNotifications(userId) {
  const [notifications, setNotifications] = React.useState([]);
  
  React.useEffect(() => {
    fetch(`/api/notifications?user=${userId}`)
      .then(res => res.json())
      .then(setNotifications);
  }, [userId]);
  
  return notifications;
}

With this approach:

  • Your directory tree instantly tells contributors where feature logic lives.
  • Notifications are isolated from blog/dashboard logic, making future split-outs (micro-frontends, Docker services) easier.
  • Cloud deployments only copy what’s required for the feature, keeping Docker images slim.

File Structure as System Design: Visual Diagram Explained in Words

Think of your app folder as the “API” for anyone working on your frontend. Imagine a tree:

  • The app/ directory is the trunk: all public routes branch from here
  • Each feature (branch) has its own leaves: nested pages, client/server logic, layouts
  • components/ is like a toolbox at the base, available to all branches
  • hooks/ (custom hooks) and utils/ (shared logic) are nutrition pipes running underground, keeping features healthy but abstracted away from the trunk

This visualization helps technical and non-technical founders alike reason about code organization, onboarding, and cloud/Docker deployment boundaries.

Conclusion: Next Steps for Founders and Technical Teams

A well-structured Next.js project is less about following arbitrary rules and more about engineering for clarity, security, and speed—whether coding solo or scaling into a fast-growing team.

Concrete, best-practice file structures pay off in:

  • Faster feature delivery (you know where to add/modify code)
  • Smoother onboarding of new engineers (documentation is implicit in the directory names)
  • Reduced bugs, better test coverage, and easier review cycles
  • Simplified, more secure Docker builds and faster cloud deployments

For founders with deep Python expertise or those running hybrid stacks, adopting these Next.js patterns allows your frontend to scale as cleanly as your backend. As a next step, regularly audit your file structure during retrospectives or roadmap planning; automated code linting and code owners can enforce consistency as your team grows.

Continue iterating—your file structure is a living system, *not* static documentation. As Next.js, Docker, and cloud best practices evolve, so should your approach.

0 Comments

Comments

Loading comments...

Popular Posts

Recent Posts

Related Posts