background-shape
Next.js 13 App Router, A Backend Dev's First Impressions
February 13, 2023 · 7 min read · by Muhammad Amal programming

TL;DR — App router is still beta as of Feb 2023 — production-ready code paths exist but expect bugs / Server components blur the backend/frontend line in genuinely useful ways / pages/ is still default for a reason; mixed-mode apps are fine.

I came to React from the backend side, which means my mental model is “the frontend is a client of my API.” Next.js 13’s app router, which Vercel released in beta at the October 2022 Next.js Conf, breaks that mental model. The server is now inside the React tree. It took me a week of skepticism before I started seeing why this matters for backend-leaning developers.

Three months in, with the app router still officially in beta as of mid-February 2023, I have opinions. This post is the take I’d give a backend engineer joining a Next.js project today — what’s worth learning now, what to defer, and where the architectural shift actually matters. It pairs with the TypeScript migration playbook from earlier this week.

The pitch, in one sentence

Server components let you write React components that run on the server, never ship to the client, and can directly call your database or backend services without going through an HTTP boundary.

That’s the whole pitch. Everything else — the app/ directory, the new layouts, streaming, suspense for data — is implementation detail around that one shift.

For a backend dev, the implication is concrete: you can write a page like this in early 2023.

// app/users/[id]/page.tsx — runs on the server
import { db } from "@/lib/db";

export default async function UserPage({ params }: { params: { id: string } }) {
  const user = await db.user.findUnique({ where: { id: params.id } });
  if (!user) return <div>Not found</div>;
  return (
    <article>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </article>
  );
}

No /api/users/[id] route. No useEffect. No loading state in the client. The page is an async function that returns React. Data fetching, rendering, and HTML serialization happen on the server, and only the resulting HTML (plus minimal hydration) ships to the browser.

Whether you find that liberating or disturbing depends on your priors. I find it liberating.

The architecture shift

The old Next.js model (still available as pages/) was:

  1. Client renders shell
  2. Client fetches data via /api/*
  3. API route runs server logic, returns JSON
  4. Client receives JSON, re-renders

That HTTP boundary in the middle was the API contract. It existed because the client couldn’t talk to the database directly.

App router collapses that for the common case. Server components run in the same process as your data layer. The “API contract” between the page and the database is now just function calls. You only build an actual HTTP API when you need to — for mobile clients, third-party consumers, or specific interactive endpoints.

This is the part that took me a week to accept. I kept thinking “but where’s the API?” The answer is: you don’t need one for the page-to-DB path. You still need one for everything that’s not a page render.

Client components are explicit now

The other half of the model is "use client". Any component that needs browser-only features — useState, useEffect, event handlers, anything that hydrates — must declare itself a client component.

// app/users/[id]/like-button.tsx
"use client";
import { useState } from "react";

export function LikeButton({ initial }: { initial: number }) {
  const [count, setCount] = useState(initial);
  return <button onClick={() => setCount(c => c + 1)}>{count} </button>;
}

You compose them inside server components:

// app/users/[id]/page.tsx
import { LikeButton } from "./like-button";

export default async function UserPage({ params }: { params: { id: string } }) {
  const user = await db.user.findUnique({ where: { id: params.id } });
  return (
    <article>
      <h1>{user!.name}</h1>
      <LikeButton initial={user!.likes} />
    </article>
  );
}

The server component renders the shell with the user’s data baked in. The LikeButton ships as a tiny interactive island. Compared to the old all-client model, you ship orders of magnitude less JavaScript for read-heavy pages.

Data fetching in server components

The new pattern uses native fetch (extended by Next.js with caching), or direct database calls. Both work.

// fetch from your own API or third-party
const res = await fetch("https://api.example.com/posts", {
  // Next.js's fetch cache, with revalidation
  next: { revalidate: 60 },
});
const posts = await res.json();

The next: { revalidate: 60 } is the new ISR (Incremental Static Regeneration) interface. The page renders fresh data on demand but caches it for 60 seconds. This is what replaces getStaticProps and getServerSideProps for the common case.

If you’re hitting your own database, skip fetch and call the query directly:

import { db } from "@/lib/db";

export default async function PostsList() {
  const posts = await db.post.findMany({
    where: { published: true },
    orderBy: { createdAt: "desc" },
    take: 20,
  });
  return (
    <ul>
      {posts.map(p => <li key={p.id}>{p.title}</li>)}
    </ul>
  );
}

No serialization round-trip. No JSON. The compiler ensures your DB types flow through your render tree.

What’s actually beta — and what to avoid

The app router being beta isn’t marketing speak. Real things are still rough as of Next.js 13.1 (Dec 2022) and 13.2 (Feb 2023):

  • Mutations. Server actions for form submissions were demoed but aren’t stable yet (they ship later in 2023). Right now, mutations still go through API routes or client-side fetch.
  • Authentication patterns. NextAuth has app router support, but it’s labeled experimental. Many auth-related cookbooks online still target the pages/ directory.
  • Streaming. The streaming/suspense story is technically there, but the error boundaries and loading UI primitives have rough edges. Expect occasional weirdness.
  • Third-party libraries. Any library that uses React context, lazy loading, or other client-only patterns needs "use client" or won’t work in server components. Some libraries haven’t shipped fixes.
  • Build perf. Cold builds on a large app router project can be slow. Vercel is shipping fixes, but it bites today.

If you’re starting a new project in February 2023 and want to ship in March, I’d still recommend pages/. If you’re starting a project meant to ship by Q3 2023, app router is the right bet — by then most of the rough edges should be smooth.

The mental migration for backend devs

The hardest part for backend-leaning devs isn’t the new APIs — it’s giving up the API boundary as a primary abstraction. A few mental models that helped me:

“Components are now your endpoints.” A server component is essentially an RPC handler that returns HTML instead of JSON. You design them with the same care as endpoints — caching, error handling, performance.

“Client components are your traditional React.” Anything in a "use client" file behaves like React always did. Hooks work. Event handlers work. State works. Just keep them small.

“The render tree is your composition root.” In old Next.js, you wired up data fetching at the page level via getServerSideProps. Now, any server component can fetch data, and the React tree composes them. This is closer to how a backend service composes — services calling services — than the previous flat API model.

Common Pitfalls

  • Forgetting "use client" on components using hooks. The error message (“hooks can only be called from a function component”) is misleading; the real issue is the file is server-rendering.
  • Crossing the boundary with non-serializable props. A server component can pass plain data (strings, numbers, plain objects) to a client component, but not functions, dates without conversion, or class instances. The compiler doesn’t always catch this; you hit it at runtime.
  • Treating server components like they have lifecycle. They don’t. No useEffect. They run once per request.
  • Importing client-only libraries into server components. Anything that touches window, document, or browser APIs will crash. Wrap those in client components.
  • Trying to use the old next/router in app router. It’s next/navigation now — useRouter, usePathname, useSearchParams from a different import. Easy to miss in tutorials still pointing at the old path.
  • Trusting cookbooks from 2022. A lot of online content predates the app router. Filter by date. The official docs are the most current source.

Wrapping Up

The app router is a real architectural shift, not a syntax change. It’s still beta, but the direction is clear, and the productivity gains for read-heavy apps are visible even at this stage. Next up in this series: React server components in more depth and the mental model for composing them.

If you’re a backend dev landing in Next.js for the first time in 2023, learn pages/ for ship-tomorrow projects, but study app/ for what’s coming.