| 1 min read

Notes on Designing a Modern Frontend Architecture

How I structure Next.js frontends to stay maintainable as product scope grows, while keeping performance and developer velocity balanced.

  • Frontend
  • Architecture
  • Next.js

Frontend architecture gets expensive when boundaries are unclear. Small shortcuts become difficult coupling points later.

I now optimize for one thing first: predictable change.

Separation by Behavior, Not by File Type

I avoid "all hooks in one folder" and "all components in one folder" structures once the app grows.

Instead, I group by domain and behavior:

  • Feature routes
  • Shared UI primitives
  • Shared data/domain utilities
  • Cross-cutting infrastructure

That pattern keeps local changes local.

Server-First Mental Model

With App Router, I default to server components and add client boundaries only when needed for interaction.

export default async function DashboardPage() {
  const data = await getDashboardData();

  return (
    <>
      <Summary data={data.summary} />
      <InteractiveChart seed={data.chart} />
    </>
  );
}

This made bundle size and hydration behavior easier to reason about.

UI Primitives Need Strong Contracts

Reusable components are only useful when their API is disciplined.

I try to keep primitives:

  • Variant-based
  • Accessible by default
  • Narrow in surface area
  • Composable through slots/children

Overly flexible components usually hide design drift.

Build Systems and Guardrails

Architecture quality drops quickly without guardrails. I rely on:

  • ESLint rules for import boundaries
  • Type-driven API contracts
  • Route-level loading/error states
  • Visual regression snapshots for core flows

The goal is not perfection. The goal is to make the wrong path harder than the right one.