Declaring redirects in next.config.ts is how you map old paths to new ones without writing any request-handling code. The function returns an array of rules, and Next.js applies them on the server before a route is rendered.
Each rule needs three fields: the incoming path pattern, the destination, and whether the move is permanent. The typed config file gives you completion and checking on all of them.
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async redirects() {
return [
{ source: '/team', destination: '/about', permanent: true },
]
},
}
export default nextConfigA request to /team now answers with a 308 status and a location header pointing at /about. The browser follows it, the address bar shows the new path, and nothing renders at the old one.
Permanent, temporary, and the status codes behind them
The permanent flag is the only field that changes the status code. Setting it to true sends a 308, and setting it to false sends a 307.
Next.js deliberately avoids the older 301 and 302 pair. Both are widely mishandled: many clients turn the redirected request into a GET even when the original was a POST, which quietly breaks form submissions and API calls.
| Flag value | Status | Cached by clients | Use it when |
|---|---|---|---|
| true | 308 | Yes, often indefinitely | The old URL is retired for good |
| false | 307 | No | The move is temporary or still being tested |
Choose false while a change is in flight. A 308 that reaches real browsers can be cached long after you remove the rule, so an incorrect permanent redirect is much harder to walk back than an incorrect temporary one.
For the rare client that cannot handle either code, a rule can carry an explicit status code instead of the permanent flag. The two fields are mutually exclusive, so use one or the other.
Matching paths with parameters
Hardcoding one rule per URL does not scale past a handful. Path patterns use the same syntax as proxy matchers, so a named parameter can capture a segment and reuse it in the destination.
// next.config.ts
async redirects() {
return [
{ source: '/blog/:slug', destination: '/news/:slug', permanent: true },
]
}A request to /blog/hiring redirects to /news/hiring, and the same rule covers every post in the section. The captured value is substituted into the destination by name.
A bare parameter matches exactly one segment. Add a star to match zero or more, which is what you want when the old section had nested paths of unpredictable depth.
Writing a pattern without the forward slash in front of a parameter makes Next.js treat the text as a literal string. The rule then matches the destination as well as the source, which is one of the fastest ways to build an infinite redirect loop.
Patterns are anchored to the start of the path, so a rule for /blog never matches /archive/blog. When you need a real expression, wrap one in parentheses after a parameter to constrain what it accepts, such as digits only for a numeric post id.
Conditional redirects with request data
A rule can require more than a path match. The has array adds conditions that must all be satisfied, and the missing array adds conditions that must all fail, before the redirect applies.
// next.config.ts
async redirects() {
return [
{
source: '/checkout/:path*',
missing: [{ type: 'cookie', key: 'cart' }],
destination: '/cart/empty',
permanent: false,
},
]
}Someone opening a checkout URL without a cart cookie is sent to an empty-cart page, while anyone carrying the cookie proceeds normally. Both conditions are evaluated against the incoming request on the server.
Each condition takes a type of header, cookie, host, or query, a key, and an optional value. Leaving the value off means any value matches, which is the right choice when presence is what you care about.
The value can also be a regular expression with a named capture group, and that captured text becomes available in the destination. That covers cases like routing a request to a locale path based on part of a header value.
Where these rules run
Config redirects are checked before the filesystem, which includes pages and files in the public folder. They also run before the proxy file, so a matching rule answers the request and the proxy function never sees it.
That ordering is a feature. A fixed path mapping belongs in configuration where it is cheap and reviewable, and the proxy file stays reserved for decisions that need the request itself.
Two details are worth knowing before you write many rules. When a base path is configured, both the source and destination are prefixed with it automatically unless you opt a rule out. And a permanent flag applies to the response only, not to the config, so removing a rule does not clear the redirect from a browser that already cached it.
Hosting platforms sometimes cap how many rules a deployment can carry, often in the low thousands. When a list grows past that cap, the usual escape hatch is to move the lookup into the proxy file and read the mapping from a data store, so check your host's own limit before a large migration.
Verifying a redirect
The fastest check is a request that does not follow the redirect, so you can read the status code and destination directly.
curl -I http://localhost:3000/teamThe response should show the status you configured and a location header with the destination. If the status is 200 instead, the rule did not match, and the pattern is the first thing to inspect.
Next.js also ships an experimental helper that runs the config routing functions without a server, which makes redirect rules unit testable.
// next.config.test.ts
import { unstable_getResponseFromNextConfig, getRedirectUrl } from 'next/experimental/testing/server'
import nextConfig from './next.config'
test('retires the team page', async () => {
const res = await unstable_getResponseFromNextConfig({ url: 'https://example.com/team', nextConfig })
expect(getRedirectUrl(res)).toBe('https://example.com/about')
})The helper evaluates only the config functions, so it proves the rule matches without proving how the deployed app behaves. Keep one real request check alongside it.
Common mistakes
Most broken redirect rules come from a small set of causes, and each has a recognizable symptom.
- A source that also matches the destination, which produces a loop the browser reports after several hops.
- A missing star modifier, so the rule covers one level and silently ignores nested paths.
- A permanent flag used during an experiment, which browsers then cache past the end of the experiment.
- A rule that expects to see a request Proxy already handled, when config redirects actually run first.
When a rule refuses to match, resist adding a second rule on top of it. Reproduce it with a single request first, since redirect loops are usually two rules disagreeing rather than one rule failing.
Rune AI
Key Insights
- The redirects function returns objects with source, destination, and permanent fields.
- Permanent true produces a 308 and false produces a 307, both of which preserve the request method.
- Path parameters and modifiers let one rule cover a whole section of a site.
- The has and missing arrays add header, cookie, query, and host conditions to a rule.
- Config redirects run before the proxy file and before filesystem routes are checked.
Frequently Asked Questions
Why does Next.js return 308 instead of 301?
Are query strings carried to the destination?
Do config redirects work in a static export?
Do config redirects run before proxy?
Conclusion
Config redirects are the right tool for path mappings you know ahead of time. Declare them in the typed config file, use the permanent flag deliberately because a 308 is cached by clients, and verify each rule with a request that shows the status code and the location header.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.