ERWAN.TECH

5 Next.js App Router Tips I Wish I Knew Earlier

Practical tips for working with Next.js 15 App Router — from server components to metadata and caching.

nextjs

Why App Router?

Since Next.js 13, the App Router has become the default way to build Next.js apps. After using it across multiple projects, here are 5 things I wish I knew from the start.

1. Server Components are the default (and that's great)

Every component in the app/ directory is a Server Component by default. This means:

  • No JavaScript shipped to the client
  • Direct access to databases and file systems
  • Better performance out of the box

Only add "use client" when you actually need interactivity:

// This runs on the server — no "use client" needed
export default async function PostList() {
  const posts = await db.posts.findMany();
 
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

2. Use generateStaticParams for static pages

If you have dynamic routes like /blog/[slug], use generateStaticParams to pre-render them at build time:

export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({ slug: post.slug }));
}

This gives you the performance of a static site with the flexibility of dynamic routes.

3. Metadata API is powerful

Stop using <Head> manually. The Metadata API handles SEO, Open Graph, and Twitter cards:

export async function generateMetadata({ params }) {
  const post = await getPost(params.slug);
  return {
    title: post.title,
    description: post.description,
    openGraph: {
      type: "article",
      publishedTime: post.date,
    },
  };
}

4. Server Actions simplify forms

No more API routes for simple form submissions. Server Actions let you call server functions directly:

async function subscribe(formData: FormData) {
  "use server";
  const email = formData.get("email") as string;
  await db.subscribers.create({ data: { email } });
}
 
export default function Newsletter() {
  return (
    <form action={subscribe}>
      <input name="email" type="email" required />
      <button type="submit">Subscribe</button>
    </form>
  );
}

5. Colocate loading and error states

The App Router lets you add loading.tsx and error.tsx files next to your pages. Use them:

app/
  blog/
    page.tsx
    loading.tsx    // Shows while page data loads
    error.tsx      // Catches errors gracefully
    [slug]/
      page.tsx

This gives you automatic Suspense boundaries without any extra code.

Wrapping up

The App Router has a learning curve, but once it clicks, it's incredibly productive. The key is to embrace server components and let Next.js do the heavy lifting.