Frontend

Next.js 13 Features and the New App Router Architecture

Next.js 13 did more than add a few APIs; it changed how pages are built. If your project still runs on <code>pages/</code>, this overview shows where the Next.js 13 features genuinely pay off and where to be careful.

  • By TETA Team
  • 8 min read
  • Updated:
Next.js 13 Features and the New App Router Architecture

What did Next.js 13 actually change?

Up to version 12, routing lived in pages/ and every page was a full React component that also hydrated in the browser. Version 13 introduced the app/ directory, built on newer React features (Server Components and Suspense), so JavaScript is only shipped for the parts that are truly interactive.

Alongside the new model came several independent improvements: next/font for loading fonts without layout shift, a lighter Image component, and a Link that no longer needs a nested <a>. Turbopack was also introduced as the future replacement for webpack. Always check details against the official Next.js docs for your own version, because defaults have changed in later releases.

The App Router and its file conventions

In the App Router every folder is a URL segment and a file's role comes from its name. Instead of repeating page scaffolding in every route, you place special files side by side:

Special files in the app directory
FileRole
page.tsxThe unique UI of that route; without it the route is not public.
layout.tsxShared shell for all child routes; it is not re-mounted on navigation.
loading.tsxFallback UI shown while data is pending (built on Suspense).
error.tsxAn error boundary for that segment, so the whole page does not break.
route.tsA Route Handler for building APIs with GET, POST and so on.

The real value of nested layouts shows up in dashboards: the sidebar renders once and only the middle content changes. A minimal root layout:

ts
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="fa" dir="rtl">
      <body>
        <header>TETA</header>
        {children}
      </body>
    </html>
  );
}

Server Components versus Client Components

A Server Component runs only on the server; it can read the database directly, use secrets, and none of its code reaches the browser. In exchange it cannot use useState, useEffect or handlers such as onClick. Wherever you need those, add "use client" at the top of the file.

ts
// app/products/page.tsx  (Server Component)
import AddToCart from './AddToCart';

export default async function Products() {
  const res = await fetch('https://api.example.com/products');
  const products: { id: number; name: string }[] = await res.json();
  return (
    <ul>
      {products.map(p => (
        <li key={p.id}>{p.name} <AddToCart id={p.id} /></li>
      ))}
    </ul>
  );
}

// app/products/AddToCart.tsx
'use client';
import { useState } from 'react';
export default function AddToCart({ id }: { id: number }) {
  const [added, setAdded] = useState(false);
  return <button onClick={() => setAdded(true)}>{added ? 'Added' : 'Add'}</button>;
}
Abstract illustration of two colourful layers, server and browser, with small blocks of code flowing between them
The more logic stays on the server, the lighter the JavaScript bundle sent to the user.

Streaming and loading states

In the old model a single slow query held up the whole page. Now, by adding a loading.tsx next to page.tsx, Next sends the page shell immediately and streams the rest over the same connection as it becomes ready.

For finer control, wrap only the slow part in Suspense so the rest of the page does not wait:

ts
import { Suspense } from 'react';
import Reviews from './Reviews'; // async Server Component

export default function ProductPage() {
  return (
    <>
      <h1>Product</h1>
      <Suspense fallback={<p>Loading reviews…</p>}>
        <Reviews />
      </Suspense>
    </>
  );
}

This pattern directly affects metrics such as LCP; to understand them, see our Core Web Vitals guide.

Data fetching, caching and metadata for SEO

Data fetching now happens inside the Server Component with async/await, replacing getServerSideProps and getStaticProps. The caching behaviour of fetch has differed between Next versions, so do not guess the default; set the cache and revalidate options explicitly.

For SEO, the Metadata API replaced next/head. Titles and descriptions can be built from the page's own data and land in the initial HTML, which is exactly what crawlers want:

ts
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';

export async function generateMetadata(
  { params }: { params: { slug: string } }
): Promise<Metadata> {
  const post = await getPost(params.slug); // your data function
  return {
    title: post.title,
    description: post.summary,
    alternates: { canonical: `/blog/${params.slug}` }
  };
}

Server rendering solves the indexing problem, but the rest still matters; the full list is in our technical SEO guide.

Migrating from pages to app

There is no need for a big-bang rewrite. Both directories work together, so you can move route by route. The order that has worked well on client projects:

  1. Create a root layout in app/ and move global fonts and styles into it.
  2. Move simple, content-heavy pages first (articles, about); low risk and immediate gain.
  3. Wrap libraries that depend on window or context in "use client" components.
  4. Rewrite complex dashboard pages last, with nested layouts.

If you are choosing a client-side state approach, React state management will help, and for structuring components read component design principles. For a larger project, take a look at our web design and development service.

Common pitfalls

  • Using useState in a Server Component and hitting an error; the fix is to split the interactive part into a small client component.
  • Passing non-serialisable data (functions, class instances) from server to client components.
  • Forgetting error.tsx and loading.tsx, leaving a poor experience on errors or slow data.
  • Assuming the default cache always matches your expectation, then seeing stale data in production.

Frequently asked questions

Did Next.js 13 replace the pages directory?

Not entirely. The app directory is the recommended path for new projects, but pages still works and you can keep both side by side while migrating gradually.

How are Server Components different from SSR?

SSR renders a component to HTML on the server, but the same code is also hydrated in the browser. A Server Component never ships its code to the browser, so the JavaScript bundle gets smaller.

Where should I use use client?

Only in components that need state, effects, event handlers or browser APIs, such as forms, dropdowns and widgets. Keep it as far down the component tree as possible.

Is the App Router better for SEO?

It does not rank pages by itself, but complete HTML, dynamic metadata and faster loading smooth the way. Content quality and site structure still decide the outcome.

Comments

    No comments yet. Be the first to share your thoughts.

    Comments are published after review.