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.
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.
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:
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.
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:
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.
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:
useAuth.js, useWindowSize.js, etc.formatCurrency.js, slugify.js, etc.
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).
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.
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.
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?
.env.* file for the target environment.
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:
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.
Keeping the public/ folder lean (only required static assets), and separating dev and prod dependencies in package.json, allows you to:
# 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.
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:
Think of your app folder as the “API” for anyone working on your frontend. Imagine a tree:
app/ directory is the trunk: all public routes branch from herecomponents/ is like a toolbox at the base, available to all brancheshooks/ (custom hooks) and utils/ (shared logic) are nutrition pipes running underground, keeping features healthy but abstracted away from the trunkThis visualization helps technical and non-technical founders alike reason about code organization, onboarding, and cloud/Docker deployment boundaries.
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:
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.
