---
title: "Redirects and Gone URLs"
description: "Redirect moved URLs, return 410 for deleted URLs, and review old entries on a schedule."
canonical_url: "https://nuxtseo.com/docs/seo-utils/guides/redirects"
---

When you move or delete a page, other sites and search results still link to the old URL.
Use `seo.redirects` to list each old URL in one place, with the date you added it.

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  seo: {
    redirects: {
      '/blog/old-post': { to: '/blog/new-post', addedAt: '2026-10-13' },
      '/spring-sale': { statusCode: 410, addedAt: '2026-10-13', reason: 'The sale ended.' },
    },
  },
})
```

Learn when to redirect and when to return an error in the [HTTP Redirects](/learn-seo/nuxt/controlling-crawlers/redirects) guide.

## Moved URLs

An entry with `to` is a moved URL. A request for the old URL gets a permanent redirect.

- The default status is `301`.
- If clients send a form `POST` to the old URL, set `statusCode: 308`. A 308 keeps the request method.
- `to` is a path on your site, or an absolute URL on another site.
- The redirect keeps the request query string. `/blog/old-post?ref=mail` redirects to `/blog/new-post?ref=mail`.

Google treats 301 and 308 the same. It uses both as a signal that the new URL is canonical.

Redirect only to a page that replaces the old one.
If you redirect many deleted pages to the home page, Google might treat the redirects as soft 404 errors.
If no page replaces the old one, use a gone URL.

## Gone URLs

An entry with `statusCode` and no `to` is a gone URL. A request for the old URL gets your error page with that status.

- If you deleted the content on purpose, use `410`.
- If you prefer the plain not found status, use `404`.

Google treats 404 and 410 the same, and removes the URL from its index.
A 410 also tells clients that the removal is likely permanent.

`reason` is a private note. The module never sends it to visitors.

## Paths and Patterns

Write each key as a path without `app.baseURL`. The module adds the base URL to each path target.

A trailing `/**` matches the path and every path below it.
To keep the rest of the path, end `to` with `/**`:

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  seo: {
    redirects: {
      // /docs/v1/setup redirects to /docs/setup
      '/docs/v1/**': { to: '/docs/**', addedAt: '2026-10-13' },
      // every path below /legacy redirects to one page
      '/legacy/**': { to: '/archive', addedAt: '2026-10-13' },
    },
  },
})
```

- A trailing slash on a request does not change the match. `/blog/old-post/` matches `/blog/old-post`.
- An exact key wins over a `/**` key. A longer `/**` key wins over a shorter one.
- Keys cannot contain a query string, a hash, `:params`, or a `*` in the middle.
- A target can contain `/**` only at its end.

### i18n

If the site uses an i18n prefix strategy, each unprefixed entry also applies under each locale prefix.
With `prefix_except_default`, `/old` redirects to `/new`, and `/fr/old` redirects to `/fr/new`.
Sites with `differentDomains` or `multiDomainLocales` get no copies.

If a locale uses a translated path, add an entry for it. An entry that starts with a locale prefix applies only to that locale.

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  seo: {
    redirects: {
      '/about-us': { to: '/about', addedAt: '2026-10-13' },
      '/fr/a-propos-de-nous': { to: '/fr/a-propos', addedAt: '2026-10-13' },
    },
  },
})
```

## Review Instead of Expiry

No entry expires on its own. A redirect keeps working until you delete it.

Google advises that you keep redirects as long as possible, generally at least one year.
For users, Google suggests that you keep them indefinitely.
After you remove a redirect, new links to the old URL stop passing signals to the new URL.

So the module schedules a review instead of an expiry.
If an entry is older than `redirectReviewAfterDays` (365 by default), `nuxt dev` and `nuxt build` list it.
The age counts from the later of `addedAt` and `reviewedAt`.

```bash
[nuxt-seo-utils]  WARN  1 seo.redirects entry is due for review (older than 365 days):
  - /blog/old-post → /blog/new-post (since 2025-10-13, 365 days)
  If an old URL still gets visits or backlinks, keep it and set reviewedAt to today.
  If it gets none, you can delete the entry.
```

To review an entry, check the recent visits to the old URL in your server logs or analytics.
Also check whether other sites still link to the old URL.

- If the old URL still gets visits or links, keep the entry and set `reviewedAt` to today. The review clock restarts from that date.
- If the old URL gets no visits and no links, you can delete the entry.

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  seo: {
    redirects: {
      '/blog/old-post': { to: '/blog/new-post', addedAt: '2025-10-13', reviewedAt: '2026-10-13' },
    },
  },
})
```

To change the window, set `redirectReviewAfterDays`. To turn off the review, set it to `false`.

### Browser Caching

Chrome can cache a 301 or 308 redirect with no end date.
So each redirect sends `Cache-Control: public, max-age=3600`.
A later fix or removal then reaches returning visitors within an hour.
On a static host, only Vercel gets this header. See [Static Hosting](#static-hosting).

If a `headers` route rule sets `Cache-Control` for the same path, the module keeps that value.

## Redirect Chains

Each hop in a chain adds a request. Google follows up to 10 hops.
Google advises that you redirect straight to the final URL.

The module flattens chains in your entries. With `/a` to `/b` and `/b` to `/c`, a request for `/a` goes to `/c` in one hop.
The build lists each chain that it flattened.

- If entries form a loop, such as `/a` to `/b` and `/b` to `/a`, the build stops with an error.
- If a moved URL redirects to a gone URL, the build warns. Pick a live target, or make the old URL a gone URL.
- If an entry is not valid, the build stops with an error.
- If `addedAt` or `reviewedAt` is not a valid date, the build warns. The entry still works, but the review skips it.

## Long Lists

For a long list, keep the entries in their own file and import it in your Nuxt config.
The `Redirects` type checks each entry.

```ts [redirects.ts]
import type { Redirects } from 'nuxt-seo-utils'

export default {
  '/blog/old-post': { to: '/blog/new-post', addedAt: '2026-10-13' },
  '/spring-sale': { statusCode: 410, addedAt: '2026-10-13' },
} satisfies Redirects
```

```ts [nuxt.config.ts]
import redirects from './redirects'

export default defineNuxtConfig({
  seo: { redirects },
})
```

## Static Hosting

A static host has no Nuxt server, so the module cannot answer each request.
When you run `nuxt generate`, the module writes the entries into the redirect file of the Nitro preset.

| Nitro preset              | File                         | Moved URL  | `/**` key | Gone URL      | `app.baseURL`      |
| ------------------------- | ---------------------------- | ---------- | --------- | ------------- | ------------------ |
| `netlify-static`          | `_redirects`                 | 301        | Yes       | 410 or 404    | Meta refresh pages |
| `vercel-static`           | `.vercel/output/config.json` | 301 or 308 | Yes       | 410 or 404    | Yes                |
| `cloudflare-pages-static` | `_redirects`                 | 301 or 308 | Yes       | Host 404 page | Yes                |

Nitro picks the preset when the build runs on the host. To pick it yourself, set `nitro.preset` or the `NITRO_PRESET` environment variable.

- Netlify has no 308 status, so Nitro writes 301. Google treats both the same.
- A gone URL serves your `404.html` page with its status. Netlify does not document 410 rules, so check the status after you deploy.
- Cloudflare Pages `_redirects` accepts only redirect status codes. A gone URL gets the host 404 page, and the build lists each 410 entry.
- With `netlify-static` and an `app.baseURL`, Nitro writes `_redirects` inside the base folder. Netlify does not read it there, so the module uses meta refresh pages.
- On Vercel, each redirect also sends `Cache-Control: public, max-age=3600`.
- Each host applies its own rules for the query string and a trailing slash.

Other presets, such as `static` and `github-pages`, write no redirect file.
For them, each exact moved URL becomes an HTML page with an instant meta refresh to the target.
Google treats an instant meta refresh as a permanent redirect.
Gone URLs and `/**` keys need rules in your host configuration, or the host returns its 404 page. The build lists these entries.

## Client-Side Navigation

The redirects run on the server. A `NuxtLink` to an old URL behaves in one of two ways:

- If no page matches the old URL, Nuxt loads the URL from the server. The redirect then applies.
- If a catch-all page such as `[...slug].vue` matches the old URL, the app renders that page. The redirect does not apply.

Update your links to point at the new URL. [Nuxt Link Checker](/docs/link-checker/getting-started/introduction) flags links to a 301 redirect.

## Other Modules

- With [Nuxt Sitemap](/docs/sitemap/getting-started/introduction), the module adds `sitemap: false` to each old URL. The sitemap then leaves it out, including its locale copies.
- The module adds `prerender: false` to each old URL, with and without `app.baseURL`. The prerender crawler then skips links to old URLs. Without this rule, it writes an HTML file that hides the redirect. A static build with meta refresh pages adds the rule to gone URLs only.

## Route Rules

Nuxt `routeRules` also support `redirect`. Choose by the kind of redirect:

- For old URLs that you moved or deleted, use `seo.redirects`. It adds dates, reviews, chain checks, and 410 support. It also adds `app.baseURL` to each path target.
- For a temporary redirect, such as a `302` during maintenance, use `routeRules` `redirect`.

## Verifying

Request the old URL and read the status and `location` header:

```bash
curl -sI https://example.com/blog/old-post | grep -iE '^(http|location)'
```

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
