Building at Scale: Lessons from 50 Enterprise Next.js Projects
Engineering

Building at Scale: Lessons from 50 Enterprise Next.js Projects

JP
James Park
VP Engineering
October 28, 20243 min read

The Problem with Most Enterprise Next.js Architectures

When teams start a Next.js project, they optimize for speed of delivery. When that project reaches 100k daily users, they optimize for survival. The gap between those two states is where most architectures fall apart.

After 50+ enterprise Next.js deployments, we've developed strong opinions about what makes the difference.

Pattern 1: Treat the App Router as a Data Fetching Framework

Next.js 13+ App Router isn't just a routing mechanism — it's a full data fetching paradigm. Teams that still think in useEffect + fetch are leaving serious performance on the table.

The key shift: Every page component should be async. Data fetching happens at the component level, not in a centralized store.

// ❌ Old pattern — client-side, waterfall, slow
export default function ProductPage({ id }: { id: string }) {
  const [product, setProduct] = useState(null)
  useEffect(() => {
    fetch(`/api/products/${id}`).then(...)
  }, [id])
}

// ✅ New pattern — server-side, parallel, fast
export default async function ProductPage({ params }: { params: { id: string } }) {
  const [product, reviews] = await Promise.all([
    getProduct(params.id),
    getReviews(params.id),
  ])
  return <ProductView product={product} reviews={reviews} />
}

Pattern 2: Cache Invalidation is a First-Class Concern

In traditional web apps, you invalidate cache when data changes. In Next.js 14+, you need a strategy for:

  • Route-level revalidationrevalidatePath() when mutations happen
  • Tag-based revalidationrevalidateTag() for fine-grained control
  • Time-based revalidationrevalidate: 60 for data that changes on a schedule

The mistake most teams make: they either over-cache (data goes stale) or under-cache (they lose all performance benefits).

Pattern 3: Separate Your Component Layers

Enterprise apps accumulate complexity fast. The teams that stay sane enforce a strict component hierarchy:

  1. Page components — async server components, data fetching only
  2. Feature components — composition, orchestration, no direct data fetching
  3. UI components — pure presentation, no side effects

When a junior engineer asks "where does this code go?" the answer should always be obvious.

Pattern 4: Build for Observability from Day One

You cannot debug a distributed Next.js application without:

  • Structured logging from every server action and API route
  • Error boundaries at the feature level, not just the page level
  • Performance marks on every critical user journey
  • Real User Monitoring — not just synthetic benchmarks

We ship every project with OpenTelemetry wired up before the first feature lands. The cost is two days. The savings when production issues hit: immeasurable.

The Pattern We Always Regret Skipping

Database access from Server Components without a proper caching layer. Every single time. ORM calls that hit the database on every request don't scale. Add a Redis layer before you need it, not after.

What Actually Predicts Success

After 50 projects, the single best predictor of a successful launch isn't tech stack choice or team size. It's whether the team has established performance budgets — hard limits on bundle size, TTFB, and CLS — and broken the build when those budgets are exceeded.

The teams that ship best enforce constraints early. The teams that struggle optimize late.

Newsletter

Get our best insights delivered weekly.

Join 5,000+ engineers and product leaders reading IntelliNodes weekly. No spam, unsubscribe anytime.

JP
James Park
VP Engineering · IntelliNodes

Engineering world-class systems and writing about what we learn along the way.