BEON.tech
TECH EXPERTISE & INNOVATION

A Next.js 16 Boilerplate That Tells You How to Work With It

Julio Lugo
Julio Lugo

Every frontend project starts the same way. Someone creates a repo, installs the framework, adds a basic page, and commits with the message “initial setup.” Then the second developer joins. And the third. And suddenly every file looks slightly different, components live in three different folders depending on who wrote them, and nobody agrees on how to test anything.

A Next.js boilerplate is supposed to solve that. Most don’t.

Some are too minimal, they install the framework and leave every real architectural decision for later. Others go too far in the opposite direction: they arrive with unwanted opinions, personal tools nobody recognizes, and abstractions that may not match the product you’re actually building.

This Next.js 16 TypeScript boilerplate takes a middle path. It’s compact but complete. Enough structure to guide a real project, without pretending every application needs a large platform from day one. More importantly, it doesn’t just provide structure. It tells developers and AI coding agents exactly how to use that structure.

The live deployment is available here.

Why Most Next.js Boilerplates Miss the Point

The real problem with most starter repositories isn’t the tech choices. It’s that they set up a structure but don’t explain it. A new developer joins the project, looks at the folders, and still has to ask: where does this component go? Who manages this state? How do we test this?

A useful Next.js boilerplate should answer those questions before they get asked. It should encode decisions directly into the repository. Through folder conventions, component templates, testing layers, and, increasingly, through instructions that AI coding agents can read and follow.

That’s what this project tries to do. The questions it answers are:

  • Where does a component belong?
  • What does a component file look like?
  • Where should domain orchestration live?
  • How do we test shared utilities versus user flows?
  • How do we deploy the app?
  • How should AI coding agents behave when they edit the repo?
  • Which design and rendering patterns are already represented?

Each answer is encoded somewhere in the repository, not in a wiki, not in a Notion doc that goes stale. In the code itself.

The Stack

The project uses Next.js 16 with the App Router and React 19. This combination gives you a modern rendering model with React Server Components, a type-safe codebase in strict TypeScript, and a global state layer that doesn’t require boilerplate beyond what the feature actually needs.

{
  "dependencies": {
    "@reduxjs/toolkit": "^2.8.0",
    "next": "^16.2.3",
    "react": "^19.2.3",
    "react-dom": "^19.2.3",
    "react-redux": "^9.2.0"
  }
}

The repository also uses ESLint 9 with eslint-config-next, TypeScript 5.9, Vitest 4, Testing Library with user-event for interaction simulation, and Playwright for end-to-end tests.

All scripts follow a consistent naming convention: test runs the full suite, while test:unit, test:integration, and test:e2e let you run each layer in isolation. Useful when debugging CI failures or iterating quickly on a single feature.

npm install
npm run dev
npm run typecheck
npm run lint
npm test
npm run test:unit
npm run test:integration
npm run test:e2e
npm run build
npm run build:pages

Project Structure

The folder layout maps directly to the architectural boundaries of the project. Each directory has a single, well-defined responsibility, which makes it easier for both humans and AI coding agents to place new files correctly.

src/app                   Next.js App Router entry points
src/components/atoms      Small reusable UI primitives
src/components/molecules  Composed UI blocks
src/components/organisms  Feature-sized UI sections
src/components/templates  Page-level layouts
src/features              Domain features, hooks, containers, and state
src/lib/patterns          JavaScript and React pattern examples
src/store                 Redux Toolkit setup
tests/unit                Vitest unit tests
tests/integration         Vitest integration tests
tests/e2e                 Playwright e2e tests
agents/shared.md          Shared AI-agent coding instructions
docs/patterns.md          Patterns.dev implementation map
.github/workflows         GitHub Pages deployment workflow

The structure is intentionally boring. That is a feature.

There are two main boundaries: src/components is for reusable UI, and src/features is for domain orchestration. Keeping those two things separate prevents the most common source of frontend entropy. Business logic leaking into UI components until nothing is testable in isolation.

Atomic Design and React Reusable Components

The component system follows atomic design. In this React 19 boilerplate, every UI element has a clearly defined layer:

  • Atoms are single-purpose primitives.
  • Molecules compose atoms into small reusable UI blocks.
  • Organisms compose atoms and molecules into feature-sized sections.
  • Templates arrange page-level structure.
  • Features contain domain-specific hooks, state, and containers.

The pattern explorer, the main feature of the app, demonstrates the full hierarchy in practice. The container owns state and orchestration. The organism renders the feature UI. The molecule renders one repeated card. The atoms handle reusable primitives like buttons.

The first screen isn’t a placeholder or a hello world. It’s a working feature that shows exactly how the layers interact with each other.

The Component Convention for React Reusable Components

One of the most underrated decisions in a frontend codebase is having a consistent component shape. When every file follows the same structure, code reviews are faster, onboarding is easier, and AI-generated changes are easier to audit.

Every React component in this boilerplate follows a consistent file layout. The VS Code snippet lives at .vscode/component.code-snippets so new components can be scaffolded in seconds.

import React from 'react';

interface IProps {}

const ComponentName: React.FC<IProps> = (props) => {
  // --- Hooks
  // --- END: Hooks
  // --- Local state
  // --- END: Local state
  // --- Refs
  // --- END: Refs
  // --- Redux
  // --- END: Redux
  // --- Data and handlers
  // --- END: Data and handlers
  // --- Side effects
  // --- END: Side effects

  return (
    <div>
      Content
    </div>
  );
};

export default ComponentName;

Every component follows the same reading order: hooks, local state, refs, Redux, data and handlers, side effects, render. The sections are always present. The props type and the return differ per component.

This is more verbose than a minimal component. But it solves a real problem. An agent or a new developer always knows where to add a hook, a memoized value, a Redux selector, or a side effect. There’s no ambiguity about where something belongs.

Redux Toolkit Setup

Redux Toolkit is installed and wired through src/store/store.ts, src/store/hooks.ts, and src/store/StoreProvider.tsx. The initial feature slice lives at src/features/patterns/patternsSlice.ts.

The slice is intentionally minimal: a single activeFilter field.

interface PatternsState {
  activeFilter: PatternFilter;
}

The goal is not to force Redux into every state problem. The goal is to show where global state belongs when a feature actually needs it. If your app only has local UI state, you don’t need Redux. When you do need it, you know exactly where to put it.

Patterns.dev as a Practical Map

One of the more opinionated decisions in this boilerplate is the inclusion of a Patterns.dev-inspired implementation map at docs/patterns.md. It documents JavaScript design patterns: Singleton, Proxy, Prototype, Observer, Module, Mixin, Mediator, Flyweight, Factory, with live implementations under src/lib/patterns/javascript.

It also maps React and Next.js patterns: Container/Presentational, HOC, Render Props, Hooks, Compound Components, and the full rendering spectrum from CSR to React Server Components and Streaming SSR.

The first five React patterns have working implementations in the codebase. The rendering patterns, SSR, ISG, Streaming, RSC, are documented as guidance in docs/patterns.md, not as runnable server routes. The app uses output: 'export' in next.config.mjs, which means all routes are statically rendered at build time. Those entries describe the approach to take when the app eventually adds dynamic server-rendered pages.

The important rule: patterns are descriptive, not mandatory. A pattern should solve a concrete problem. This project documents and demonstrates them, it doesn’t encourage forcing them into every file.

The Pattern Explorer UI

The home page renders a working pattern explorer. It’s the actual app surface, not a marketing page.

The architecture follows the atomic design hierarchy: the page template at src/components/templates/PatternsPageTemplate/, the container at src/features/patterns/containers/PatternExplorerContainer.tsx, and the presentational organism at src/components/organisms/PatternExplorer/PatternExplorer.tsx.

The UI renders a heading, a short description, filter buttons, a pattern count, and cards for each mapped pattern.

The filter buttons use aria-pressed to give the controls an accessible selected state. This is a small but important detail: keyboard and screen reader users get the same interaction feedback as mouse users, and it made the Playwright assertions cleaner because the selected state is queryable directly from the DOM.

<button
  aria-pressed={isSelected}
  className={classes}
  type={type}
  {...buttonProps}
>
  {children}
</button>

The project uses CSS Modules for component styles. Global variables live in src/styles/globals.css. The styling is intentionally restrained, this is a Next.js boilerplate for operational app work, not a visual experiment.

Testing Strategy: Three Layers, Three Risk Surfaces

Most frontend projects either skip testing entirely or treat it as a single category. This boilerplate makes a clearer distinction: three testing layers, each covering a different risk surface.

Unit Tests

Unit tests live in tests/unit. The JavaScript pattern tests verify pure utilities: factory creation, observer notification, proxy validation, flyweight reuse. The Button test verifies rendering, click behavior, and selected state. These tests are fast, isolated, and have no dependencies on the browser or Redux.

Integration Tests

Integration tests live in tests/integration. The main test renders the pattern explorer container with Redux and verifies that clicking the React filter updates visible cards through the Redux-backed UI state. This is the layer that catches wiring bugs, the ones that only appear when components, state, and handlers are connected.

E2E Tests

Playwright tests live in tests/e2e. The e2e test visits the home page, checks that the explorer renders, clicks a filter, and verifies the visible result.

The Playwright config connects to the dev server rather than a production build. That keeps the e2e layer fast to iterate on locally, while the CI workflow handles the full production build separately.

use: {
  baseURL: 'http://localhost:3000',
  trace: 'on-first-retry',
}

Using localhost matters because Next.js dev mode can block cross-origin dev resource access when the page is loaded through 127.0.0.1.

AI-Agent Instructions: One Source of Truth

One of the more unusual parts of this Next.js 16 repo is the agents setup , and probably the most transferable idea in the whole project.

Most AI-assisted codebases accumulate tool-specific context files that slowly drift apart. CLAUDE.md says one thing. The Cursor rules say another. The Copilot instructions are six months out of date. Each tool builds a slightly different mental model of the codebase, and the inconsistencies compound over time.

The fix here is minimal: one canonical file (agents/shared.md), four thin entry points that defer to it.

  • CLAUDE.md
  • CODEX.md
  • AGENTS.md
  • .cursorrules

Every tool reads the same rules. When the rules change, they change in one place. The shared instructions cover the component convention, atomic design placement, Patterns.dev alignment, Next.js server/client defaults, CSS Module usage, TypeScript expectations, testing expectations, and deployment behavior.

This is not a framework. It’s a discipline. And it’s easy to replicate in any existing repo today.

Static Export and GitHub Pages Deployment

GitHub Pages serves static files, so the app needs to export static output. The next.config.mjs file has three settings that matter specifically for static hosting, each one exists for a concrete reason.

const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';

const nextConfig = {
  basePath,
  images: { unoptimized: true },
  output: 'export',
  reactStrictMode: true,
  trailingSlash: true,
};

output: 'export' tells Next.js to emit static files. images.unoptimized avoids runtime image optimization, which is not available on GitHub Pages. basePath lets the same app work under a project URL like /nextjs-16-boilerplate. Necessary because project Pages URLs are served from https://username.github.io/repository-name/.

The deployment workflow runs on every push to main and can also be triggered manually from the Actions tab. It runs the full quality check suite before building: typecheck, lint, and tests. The deployed site is always generated from the latest passing main.

One step in the workflow is worth highlighting: the base path detection. It reads the repository name from the GITHUB_REPOSITORY environment variable and sets NEXT_PUBLIC_BASE_PATH automatically.

repo_name="${GITHUB_REPOSITORY#*/}"
if [[ "${repo_name}" == *.github.io ]]; then
  echo "NEXT_PUBLIC_BASE_PATH=" >> "$GITHUB_ENV"
else
  echo "NEXT_PUBLIC_BASE_PATH=/${repo_name}" >> "$GITHUB_ENV"
fi

Fork the repo, rename it, and the deployment picks up the correct base path without touching the workflow file.

CI and Lockfile Details

One CI issue that came up is worth documenting. The GitHub runner used npm 11.11.0, and the initial lockfile was missing nested optional dependencies required by npm 11’s stricter install behavior. The fix: regenerate the lockfile with the same npm version the runner uses.

npx npm@11.11.0 install --package-lock-only
npx npm@11.11.0 ci --ignore-scripts

If CI uses a different npm version than your machine, lockfile behavior can differ silently. This project uses Node 24, which currently ships with npm 11 on GitHub-hosted runners.

What This Next.js Boilerplate Is Good For and What It Isn’t

This repo works well as a starting point for internal tools, dashboards, product prototypes, documentation-style apps, UI architecture experiments, agent-assisted frontend projects, and small production apps that can start static.

It is especially useful when you want conventions from day one: a known component shape, a known folder strategy, a known testing strategy, a known deployment strategy, and a known way for AI coding agents to contribute.

This boilerplate is meant to be a disciplined starting point rather than a full product platform. It does not include authentication, database access, API route examples, form libraries, design system tokens beyond simple CSS variables, visual regression testing, Storybook, analytics, or error reporting. Those can be added when the product needs them. The point of this React 19 boilerplate is to provide a disciplined base not to predict every future requirement.

The most important part of this Next.js boilerplate is not any single dependency or pattern. It is that the repository explains how it wants to be extended. New developers know where things go. AI agents know how to contribute. The codebase teaches itself.

That is what makes a boilerplate useful after the first commit.

FAQs

Can I use this boilerplate as the foundation for a production app?

Yes, with the right scope in mind. This Next.js boilerplate is well-suited for internal tools, dashboards, prototypes, and small production apps that can start as static exports. It gives you a tested, typed, and structured base from day one. For apps that require authentication, database access, or server-side API routes, you’ll need to add those layers but the folder structure and conventions scale cleanly to support them.

Does the atomic design system work with a component library like Shadcn or MUI?

Yes. The atomic design structure is organizational, not visual. If you bring in an external component library, your atoms can wrap those components with your own typed props interface and styling layer. The convention stays the same, you’re just composing from a different set of primitives. The component template at .vscode/component.code-snippets works regardless of what’s inside the return statement.

What happens to the AI-agent instructions when the codebase evolves?

That’s exactly the point of the agents/shared.md pattern. Instead of updating four separate tool-specific files every time a convention changes, you update one file and all tools stay in sync. The entry points, CLAUDE.md, CODEX.md, AGENTS.md, .cursorrules are thin wrappers that defer to the shared file. Maintaining agent context becomes a single-file responsibility, not a coordination problem.

Can I deploy this to a hosting platform other than GitHub Pages?

Yes. The output: 'export' config in next.config.mjs generates a static out/ directory that can be served from any static hosting provider . Vercel, Netlify, Cloudflare Pages, S3, or any CDN. The GitHub Pages workflow is specific to that deployment target, but the build output is platform-agnostic. If you deploy to Vercel, you can remove the output: 'export' setting entirely and unlock dynamic server-rendered routes.

Ready to build your team in Latin America?

Let us connect you with pre-vetted senior developers who are ready to make an impact.

Get started
Julio Lugo
Written by Julio Lugo

Julio Lugo is a Software Engineer at BEON.tech, AWS Certified Solutions Architect, and a Georgia Tech OMSCS student. He specializes in frontend architecture and performance optimization, having led key initiatives to modernize build pipelines and improve application speed and reliability.