# Dynamic Route in Next JS: Best Practices for Data Fetching and Rendering

> Master dynamic routes in Next JS! Optimize performance with Static Generation ISR SWR and shallow routing for efficient data fetching and rendering. Unlock best practices now.

- Repository: [Vercel/next.js](https://github.com/vercel/next.js)
- Tags: best-practices
- Published: 2026-02-16

---

**For optimal performance in a dynamic route in Next JS, use Static Generation with Incremental Static Regeneration for cacheable content, hydrate with SWR or React Query for client-side updates, and leverage shallow routing for UI-only state changes to avoid unnecessary server fetches.**

Implementing a dynamic route in Next JS requires careful consideration of when and where data fetching occurs to maximize performance. According to the Next.js source code, the framework supports multiple rendering strategies—from Static Generation to Edge SSR—that determine how dynamic segments like `[id].tsx` resolve and hydrate on the client.

## Rendering Strategies for Dynamic Routes

Next.js matches a request to a dynamic route via the **dynamicRoutes** step in the router, as implemented in [`packages/next-routing/README.md`](https://github.com/vercel/next.js/blob/main/packages/next-routing/README.md). The server extracts parameters (such as `id` in `[id].tsx`) and makes them available through `router.query` or `params`.

### Static Generation and Incremental Static Regeneration

For content that is publicly cacheable and does not need to be fresh on every request, use **Static Generation (SSG)** with `getStaticProps` and `getStaticPaths`. This generates HTML at build time and caches it at the edge.

```tsx
// pages/posts/[id].tsx – SSG example
// https://github.com/vercel/next.js/blob/canary/examples/with-mongodb-mongoose/pages/%5Bid%5D/edit.tsx
export async function getStaticPaths() {
  const ids = await fetch('https://example.com/api/ids').then(r => r.json())
  return {
    paths: ids.map((id: string) => ({ params: { id } })),
    fallback: 'blocking',
  }
}

export async function getStaticProps({ params }: { params: { id: string } }) {
  const post = await fetch(`https://example.com/api/posts/${params.id}`).then(r => r.json())
  return {
    props: { post },
    revalidate: 60,
  }
}

```

The `revalidate` option enables **Incremental Static Regeneration (ISR)**, allowing the page to regenerate in the background at most every 60 seconds. This balances performance with freshness for mostly-static data like blog posts or marketing pages.

### Server-Side Rendering

For data that must be up-to-date on every request—such as authentication-protected pages or personalized content—use **Server-Side Rendering (SSR)** with `getServerSideProps`. This function runs on every request in [`packages/next/src/server/lib/router-server.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/server/lib/router-server.ts), producing fresh HTML.

```tsx
export async function getServerSideProps(context) {
  const session = await getSession(context.req)
  if (!session) {
    return { redirect: { destination: '/login', permanent: false } }
  }
  const data = await fetchUserData(session.userId)
  return { props: { data } }
}

```

Keep this function lightweight to avoid blocking the response pipeline. Heavy computation should be moved to a separate API route or Edge function.

## Client-Side Performance Optimizations

After the initial server render, client-side data fetching strategies determine how the application stays responsive and up-to-date without sacrificing the benefits of static caching.

### Hydration with SWR

Use **SWR** (or React Query) to fetch fresh data after the page loads. Seed the hook with the statically-generated payload using `fallbackData` to prevent a flash of loading state.

```tsx
import useSWR from 'swr'
import { useRouter } from 'next/router'

const fetcher = (url: string) => fetch(url).then(r => r.json())

export default function Post({ post: initialPost }) {
  const { query } = useRouter()
  const { data: freshPost, error } = useSWR(
    query.id ? `/api/posts/${query.id}` : null,
    fetcher,
    { fallbackData: initialPost, revalidateOnFocus: false }
  )

  if (error) return <p>Failed to load.</p>
  if (!freshPost) return <p>Loading…</p>

  return (
    <article>
      <h1>{freshPost.title}</h1>
      <p>{freshPost.content}</p>
    </article>
  )
}

```

Setting `revalidateOnFocus: false` prevents unnecessary network calls when the user returns to the tab, though you can enable it for absolute freshness.

### Shallow Routing for UI State

When only the URL query string changes—such as during pagination or filtering—use **shallow routing** to update the UI without re-running data-fetching methods.

```tsx
import { useRouter } from 'next/router'

function Pagination({ page }) {
  const router = useRouter()

  const goToPage = (p: number) => {
    router.push(
      { pathname: router.pathname, query: { ...router.query, page: p } },
      undefined,
      { shallow: true }
    )
  }

  return <button onClick={() => goToPage(page + 1)}>Next</button>
}

```

With `shallow: true`, `getStaticProps` or `getServerSideProps` are not re-executed, providing instant UI updates while the client fetches new data independently.

## Edge Runtime Considerations

For globally distributed users, run `getServerSideProps` in the **Edge runtime** to reduce latency. Configure this in [`packages/next/src/pages/_middleware.tsx`](https://github.com/vercel/next.js/blob/main/packages/next/src/pages/_middleware.tsx) patterns.

```tsx
// pages/geo/[country].tsx
export const config = { runtime: 'edge' }

export async function getServerSideProps({ params }) {
  const res = await fetch(`https://geo.api/${params.country}`)
  const geo = await res.json()
  return { props: { geo } }
}

export default function Country({ geo }) {
  return (
    <>
      <h2>{geo.name}</h2>
      <p>Population: {geo.population}</p>
    </>
  )
}

```

The Edge runtime moves computation closer to the user but requires Edge-compatible APIs only—avoid Node-specific modules.

## Key Implementation Files

| Path | Purpose |
|------|---------|
| [`packages/next-routing/README.md`](https://github.com/vercel/next.js/blob/main/packages/next-routing/README.md) | Documents the **dynamicRoutes** matching step that extracts URL parameters. |
| [`packages/next/src/server/lib/router-server.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/server/lib/router-server.ts) | Core server logic that resolves requests to dynamic pages and wires `getStaticProps` / `getServerSideProps` into the response pipeline. |
| `examples/with-mongodb-mongoose/pages/[id]/edit.tsx` | Real-world implementation showing `useRouter` and client-side data fetching patterns. |
| [`test/unit/next-babel-loader-dev.test.ts`](https://github.com/vercel/next.js/blob/main/test/unit/next-babel-loader-dev.test.ts) (lines 252-279) | Demonstrates how the bundler treats data-fetching methods during development to ensure production parity. |
| [`test/integration/server-side-dev-errors/pages/gsp.js`](https://github.com/vercel/next.js/blob/main/test/integration/server-side-dev-errors/pages/gsp.js) | Example page exporting `getStaticProps` with associated error handling paths. |

## Summary

- **Choose the right rendering mode**: Use Static Generation with ISR for cacheable content, SSR for request-specific data, and Edge runtime for global low-latency responses.
- **Leverage shallow routing**: Update query parameters without re-running `getStaticProps` or `getServerSideProps` to maintain instant UI responsiveness.
- **Hydrate intelligently**: Use SWR with `fallbackData` to merge server-rendered props with client-side updates without loading flashes.
- **Keep server functions lightweight**: Heavy computation belongs in API routes or background jobs, not in `getServerSideProps`.

## Frequently Asked Questions

### What is the difference between `getStaticProps` and `getServerSideProps` in a dynamic route?

`getStaticProps` runs at build time (or revalidation time for ISR) to generate static HTML that can be cached by CDNs, making it ideal for content that does not change frequently. `getServerSideProps` executes on every incoming request on the server, ensuring fresh data for personalized or authentication-protected pages, but it cannot be cached at the edge and increases server load.

### When should I use shallow routing in Next.js dynamic routes?

Use shallow routing when you need to update the URL query string—such as for pagination, filters, or tabs—without triggering a full page reload or re-running data-fetching methods like `getStaticProps`. This keeps the client-side state synchronized with the URL while maintaining the performance benefits of your initial server render.

### How does Incremental Static Regeneration improve performance for dynamic routes?

Incremental Static Regeneration (ISR) allows a statically generated page to be updated in the background at a specified interval (using the `revalidate` option) without requiring a full site rebuild. This means users receive a cached, instant HTML response while the server prepares an updated version for subsequent requests, balancing performance with content freshness.

### Can I use the Edge runtime with `getServerSideProps` in dynamic routes?

Yes, you can configure `getServerSideProps` to run in the Edge runtime by exporting `config = { runtime: 'edge' }` from your page file. This executes the data-fetching logic at edge locations closer to the user, reducing latency, but you must ensure your code uses only Edge-compatible APIs and avoids Node.js-specific modules.