This episode covers application architecture and feature-based structure, modular component design with atomic patterns, domain-driven organization, and design systems and UI component libraries for applications that are easy to scale.

Small applications are easy to write, but large applications have to be maintained. When code grows by thousands of lines, bad structure makes every change risky and slows the team down. Architecture is the answer: structured decisions about how code is organized.
Episode 18 covers application architecture and feature-based structure, modular component design with atomic patterns, domain-driven organization, and design systems and UI component libraries.
Instead of grouping files by technical type (all components in one folder), feature-based organization groups files by business domain:
app/
auth/
blog/
dashboard/
components/
ui/
forms/
layout/
lib/
api/
utils/
auth.tsThe structure above keeps each feature's routes, components, and logic close together. When the blog feature grows, all its related blog code grows together without touching other features — making refactoring and onboarding new developers easier. This structure also makes removing a feature simple: delete the feature folder along with everything inside it, without worrying about leftover code scattered around.
Server and client components also affect the structure: data logic lives on the server, interactions on the client. This separation maintains the security boundary while clarifying each file's responsibility.
Evaluate the architecture periodically: do the existing folders still reflect the team's needs? Architecture is a living document that must adapt, not an artifact frozen at the start of a project.
The App Router provides route groups with parenthesized folders, for example (marketing) and (app). Route groups organize files without adding a URL segment, while also allowing separate layouts between groups — a pattern often used to separate public pages from protected pages. The (app) group can carry its own dashboard layout, while (marketing) uses a public layout — both still share the same root layout.
The atomic design pattern divides components into five levels: atoms, molecules, organisms, templates, and pages. For example:
This pattern encourages small, reusable components. Primitive components like Input and Button are examples of atoms usable across every feature. Because JSX in Next.js is identical to React, all these patterns apply directly to both server and client components.
Modular components are designed through composition — combining small components — rather than props that keep growing. Use the children prop to let the caller decide a component's content, and explicit props only for options the component actually controls. Separate presentational components (display only) from container components (which fetch data). This keeps display components easy to test and reuse in various contexts.
Domain-driven design (DDD) organizes code around business domains: healthcare, finance, education, and so on. Each domain shares a common language between developers and stakeholders, and each module has a clear boundary. In Next.js, this means feature folders reflect domains, and lib/ is split per domain instead of being one mixed-up util folder. Start with the most complex domain — usually the one that changes most often — and create clear boundaries between domains and infrastructure like databases and external APIs so changes on one side don't leak into the other.
For truly large projects, consider a monorepo with pnpm workspaces or Turborepo — separating the Next.js application, component libraries, and APIs into packages with their own versioning. At medium scale, a simple feature-based structure is enough; DDD and monorepos are tools for genuinely large projects.
A design system is a collection of tokens, components, and guidelines that maintain consistency. Its main components:
Tokens are defined in CSS:
:root {
--color-primary: #2563eb;
--radius-md: 8px;
--font-sans: "Inter", system-ui, sans-serif;
}The CSS variables above are used by all components, so a branding change only requires editing one place. Tokens separate visual decisions from component implementation. Start with tokens, then primitive components, then complex components — don't build the whole system at once; develop based on real feature needs, not speculation.
Building a design system from scratch requires a big investment. A pragmatic alternative: use libraries like shadcn/ui, which provides accessible components you can copy into your codebase, or Radix UI as a headless foundation that doesn't dictate styling. Start with a library, then customize gradually for brand needs.
Primitive components must be accessible from the start — fixing accessibility at the end costs far more than building it correctly.
The same pattern applies to tokens: define only what's used, and clean up what isn't, to keep the system lightweight.
Here's what to take away:
In the next episode, episode 19, we'll discuss modern tooling and build automation — build tooling with Turbopack, strict TypeScript configuration, CI/CD pipelines for Next.js projects, and linting, formatting, and pre-commit hooks. Your development workflow will be automated.