← Back to portfolio

Blog

Thoughts on development, design, and technology

← All posts
6 min readby Zakkarija Micallef

Building with Next.js 15: App Router Deep Dive

Exploring the new features and patterns in Next.js 15, including the App Router, Server Components, and improved developer experience.

Next.jsReactWeb DevelopmentApp Router
Building with Next.js 15: App Router Deep Dive

Building with Next.js 15: App Router Deep Dive

Next.js 15 brings significant improvements to the developer experience, particularly with the App Router becoming stable and more powerful. Here's what I've learned building my portfolio with the latest version.

App Router vs Pages Router

The App Router represents a fundamental shift in how we structure Next.js applications. Instead of file-based routing in a pages directory, we now use:

src/app/
  layout.tsx      // Root layout
  page.tsx        // Home page
  blogs/
    layout.tsx    // Blog-specific layout
    page.tsx      // Blog listing
    [slug]/
      page.tsx    // Dynamic blog post pages

Server Components by Default

One of the biggest changes is that components are Server Components by default. This means:

  • They run on the server during build time or request time
  • They can directly access databases or APIs
  • They don't ship JavaScript to the client
  • They can't use browser-specific APIs or event handlers

When you need client-side interactivity, you explicitly opt in with 'use client':

'use client';
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  // This runs in the browser
}

Layouts: A Game Changer

Nested layouts are incredibly powerful. In my blog section, I have:

  1. Root layout (app/layout.tsx) - Sets up the HTML structure, fonts, analytics
  2. Blog layout (app/blogs/layout.tsx) - Adds newspaper-style header, different color scheme

This creates a clean separation between the main portfolio (dark theme) and blog (light theme) while sharing the root layout's global styles and metadata.

Static Generation with Dynamic Routes

For blog posts, I use generateStaticParams to pre-build all post pages:

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

This gives us the performance benefits of static generation while supporting dynamic content.

Metadata API

The new Metadata API is much cleaner than the old getStaticProps approach:

export async function generateMetadata({ params }) {
  const post = await getBlogPost(params.slug);
  
  return {
    title: `${post.title} - Zakkarija Micallef`,
    description: post.excerpt,
  };
}

Performance Insights

The App Router includes several performance improvements:

  • Automatic code splitting at the layout level
  • Streaming support out of the box
  • Parallel route loading for faster navigation
  • Optimistic updates with Server Actions

Developer Experience

Development with Next.js 15 feels smoother:

  • Turbopack for faster development builds (next dev --turbo)
  • Better error messages and stack traces
  • Improved TypeScript integration
  • Hot reload works more reliably

Challenges and Considerations

Not everything is perfect. Some considerations:

  1. Learning curve - Understanding when to use Server vs Client Components
  2. Third-party libraries - Many aren't optimized for Server Components yet
  3. Debugging - Server Component errors can be harder to trace
  4. Mental model shift - Requires thinking differently about data flow

Conclusion

Despite the learning curve, Next.js 15 with the App Router feels like a significant step forward. The performance benefits, cleaner architecture, and improved developer experience make it worth the migration effort.

The key is to embrace the new mental model: start with Server Components for performance, and only reach for Client Components when you need browser APIs or interactivity.


What's your experience been with the App Router? I'd love to hear about the challenges and wins you've encountered.