Next.js themed hero image used for an article about routing and SEO.
← Back to Blog

Understanding Next.js Routing

How I think about route groups, dynamic segments, metadata, and SEO when building with the Next.js App Router.

December 05, 20246 min read

Routing Is a Product Decision

In Next.js, the route tree is not just a technical detail. It shapes discoverability, internal linking, metadata strategy, and how easily someone can understand your product from the URL alone.

Clean route design makes the app easier for both users and search engines. If a URL structure is confusing to read, it is usually confusing in the codebase too.

Use the App Router Deliberately

Route groups, nested layouts, templates, and dynamic segments are powerful, but they only help when each layer has a clear responsibility. Layouts should own shared structure, pages should own page meaning, and metadata should live as close as possible to the content it describes.

That separation becomes especially important for blogs, project pages, and collection pages where titles, canonical URLs, and structured data need to change per entry.

Dynamic Routes Need Real Metadata

The common mistake is to ship one generic metadata object for every dynamic route. That leaves search engines with weak titles and duplicate descriptions even when the page content is unique.

If a page is generated from a slug, its metadata should be generated from the same source of truth. That includes title, description, canonical path, social image, robots policy, and any structured data attached to the page.

Routing and Crawlability

JavaScript-only navigation can still hurt discoverability when core pages are hidden behind client-side state or button handlers. Important content should remain reachable through standard links in the HTML.

For portfolios and blogs, that usually means rendering collection pages server-side and making every detail page reachable from a normal anchor or Next.js Link component.

src/blog/nextjs-routing.tsx
import { useRouter } from 'next/router';const router = useRouter();const { slug } = router.query;