How to Create a Next.js Redirect From One Page to Another: 3 Methods Explained

To redirect users between pages in Next.js, use redirect() from next/navigation for App Router Server Components, NextResponse.redirect() from next/server for Middleware or API routes, or define static redirects in next.config.js for build-time SEO optimization.

Redirecting traffic between routes is essential for authentication flows, legacy URL support, and post-submission navigation. The vercel/next.js repository implements nextjs redirect functionality across three distinct architectural layers, each optimized for specific runtime environments. Every approach ultimately delivers HTTP Location headers with appropriate status codes (307, 308, or 303), but the implementation path differs based on whether you need immediate server-side logic, edge-level interception, or permanent SEO-friendly routing.

Server-Side Redirects in the App Router

The App Router provides the redirect() utility for immediate navigation inside Server Components and Server Actions.

Using redirect() in Server Components

When you need to conditionally send users to another page during server rendering, import redirect from next/navigation. According to the source code in packages/next/src/server/lib/router-utils/typegen.ts (lines 356–368), this helper constructs a <meta http-equiv="refresh"> tag for Server Components and a Response object with a Location header for Server Actions.

// app/dashboard/page.tsx
import { redirect } from 'next/navigation'

export default async function DashboardPage() {
  const hasAccess = await checkUserAccess()
  if (!hasAccess) {
    // 307 temporary redirect to the login page
    redirect('/login')
  }
  return <h1>Dashboard</h1>
}

Behind the scenes, the server emits an x-nextjs-redirect header that the client-side router consumes to perform the navigation without a full page reload.

Handling Form Submissions with Server Actions

For form POST workflows, redirect() is particularly useful inside Server Actions to prevent duplicate submissions on refresh. The implementation generates a 303 status code (See Other) when using the RedirectType.replace option, ensuring the browser converts the POST request to a GET request at the destination.

// app/contact/action.ts
'use server'
import { redirect } from 'next/navigation'

export async function submitContact(formData: FormData) {
  await sendEmail(formData)
  // After a successful POST we want a 303 redirect (POST → GET)
  redirect('/thanks', RedirectType.replace) // uses RedirectType.replace = 303
}

Edge and Middleware Redirects

For logic that must run before a request reaches your page components, use NextResponse.redirect() inside middleware.ts or API routes. The static method is defined in packages/next/src/server/web/spec-extension/response.ts (lines 117–122), where it creates a standard Web API Response with the correct status and Location headers.

// middleware.ts
import { NextResponse } from 'next/server'

export function middleware(request) {
  const url = request.nextUrl
  if (url.pathname.startsWith('/old-blog')) {
    // 308 permanent redirect preserving the path
    return NextResponse.redirect(new URL('/new-blog' + url.pathname.slice(9), request.url), 308)
  }
  return NextResponse.next()
}

This approach executes at the edge, allowing you to rewrite or redirect based on geolocation, authentication cookies, or A/B testing criteria before the request hits your application code.

Static Redirects in next.config.js

For permanent, SEO-critical URL migrations that should be applied at build time, define your nextjs redirect rules in next.config.js. The configuration is processed in packages/next/src/server/route-modules/pages/pages-handler.ts (lines 659–686), where each entry is transformed into a server-side redirect response during the build process.

// next.config.js
module.exports = {
  async redirects() {
    return [
      {
        source: '/legacy/:path*',
        destination: '/new/:path*',
        permanent: true, // 308
      },
    ]
  },
}

Static redirects are ideal for domain migrations, restructuring content hierarchies, or deprecating old marketing pages, as they generate the proper HTTP responses without requiring runtime JavaScript execution.

How Next.js Redirects Work Under the Hood

Regardless of the API you choose, all redirects in Next.js converge on the same client-side handling mechanism. While server-side logic (Server Components, middleware, or config handlers) emits HTTP responses with Location headers, the client-side router in packages/next/src/shared/lib/router/router.ts (lines 294–311) specifically checks for the x-nextjs-redirect header. When detected, the router performs an internal navigation update using window.location.replace() or standard history manipulation, preserving application state and avoiding unnecessary full-page refreshes.

The redirect() utility for the App Router differs slightly based on context: it injects a meta refresh tag for streaming Server Components to ensure compatibility with streaming protocols, while it returns a proper HTTP Response object for Server Actions to satisfy form POST semantics.

Summary

  • redirect() from next/navigation: Use inside async Server Components and Server Actions for immediate, conditional navigation (307/303 status codes).
  • NextResponse.redirect() from next/server: Use inside middleware.ts or API routes for edge-runtime redirects that run before page rendering (307/308 status codes).
  • redirects in next.config.js: Use for permanent URL migrations applied at build time, ensuring SEO authority transfer with 308 permanent redirects.
  • Client handling: All methods ultimately trigger the client-side router via x-nextjs-redirect headers, enabling seamless navigation without full page reloads.

Frequently Asked Questions

What is the difference between 307 and 308 redirects in Next.js?

307 (Temporary Redirect) instructs the browser to maintain the original HTTP method (e.g., POST remains POST), while 308 (Permanent Redirect) also preserves the method but signals to search engines that the resource has permanently moved. For static redirects in next.config.js, setting permanent: true generates a 308 status code, whereas permanent: false generates a 307. Legacy 301/302 codes are available for Pages Router compatibility but are not recommended for new App Router implementations.

When should I use next.config.js redirects versus middleware?

Use next.config.js for static, unchanging URL mappings that can be determined at build time, such as migrating /old-product to /new-product. This approach requires no runtime computation and offers the best performance. Use middleware when the redirect logic depends on request-time data like cookies, headers, or geolocation, or when you need to enforce authentication gates before route access.

How does the client-side router handle server-side redirects?

The client-side router defined in packages/next/src/shared/lib/router/router.ts intercepts responses containing the x-nextjs-redirect header (emitted by redirect() or static config handlers) and performs a client-side navigation using the history API. This prevents the browser from performing a full document reload, preserving React state and enabling smooth transitions while still updating the URL and issuing the appropriate navigation event.

Can I redirect from a Server Action after form submission?

Yes. Server Actions support redirect() from next/navigation immediately after asynchronous operations complete. As implemented in packages/next/src/server/lib/router-utils/typegen.ts, the function generates a 303 See Other response when passed RedirectType.replace, ensuring the browser converts the POST request to a GET request at the destination URL. This prevents the "form resubmission" warning when users refresh the success page.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s https://instagit.com/install.md

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client