HTTP Redirects for SEO in Vue · Nuxt SEO

-
-
-
-

[1.4K](https://github.com/harlan-zw/nuxt-seo)

[Nuxt SEO on GitHub](https://github.com/harlan-zw/nuxt-seo)

Learn SEO

Master search optimization

Nuxt

 Vue

-
-
-
-
-
-
-

-
-
-
-
-
-
-

-
-
-

-
-
-
-
-
-
-
-
-
-
-

-
-
-

-
-
-
-
-
-
-
-
-

1.
2.
3.
4.
5.

# HTTP Redirects for SEO in Vue

301 redirects preserve SEO value when content moves. Implement server-side redirects in Vue SSR to pass link equity and maintain rankings.

[![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan-zw)9 mins read Published Nov 3, 2024 Updated Jan 29, 2026

What you'll learn

- 301 redirects pass ~100% link equity. use for permanent content moves
- Server-side redirects required for SEO (JavaScript redirects don't pass link equity)
- Avoid redirect chains (A→B→C). redirect directly to the final destination

301 redirects pass nearly 100% of link equity to the new URL ([Google confirms](https://www.searchenginejournal.com/301-redirect-pagerank/275503/)), preserving your SEO value when content moves. Server-side implementation required for search engines to recognize them.

Use for permanent moves: site migrations, URL restructuring, domain changes, deleted pages with replacements. For duplicate content use

. For temporary moves use 302 redirects.

## [Quick Setup](#quick-setup)

In Vue applications, implement redirects at the server level:

Express

Vite

H3

```
import express from 'express'

const app = express()

app.get('/old-page', (req, res) => {
  res.redirect(301, '/new-page')
})

app.get('/blog/:slug', (req, res) => {
  res.redirect(301, \`/articles/${req.params.slug}\`)
})
```

```
// server.js for Vite SSR
import express from 'express'

const app = express()

app.use((req, res, next) => {
  if (req.path === '/old-page') {
    return res.redirect(301, '/new-page')
  }
  if (req.path.startsWith('/blog/')) {
    const slug = req.path.replace('/blog/', '')
    return res.redirect(301, \`/articles/${slug}\`)
  }
  next()
})
```

```
import { defineEventHandler, sendRedirect } from 'h3'

export default defineEventHandler((event) => {
  if (event.path === '/old-page') {
    return sendRedirect(event, '/new-page', 301)
  }
  if (event.path.startsWith('/blog/')) {
    const slug = event.path.replace('/blog/', '')
    return sendRedirect(event, \`/articles/${slug}\`, 301)
  }
})
```

## [301 vs 302 Redirects](#_301-vs-302-redirects)

### [When to Use Each](#when-to-use-each)

**301 (Permanent)** - Transfers ~100% of link equity to new URL ([Google](https://www.searchenginejournal.com/301-redirect-pagerank/275503/))

- Permanent content moves
- Domain migrations
- URL structure changes
- Deleted pages with direct replacements
- HTTP to HTTPS upgrades

**302 (Temporary)** - Keeps SEO value on original URL

- A/B testing
- Temporary promotions
- Maintenance pages
- Out-of-stock product redirects

If a 302 stays active for months with no plans to revert, switch to 301 ([SEO Clarity](https://www.seoclarity.net/resources/knowledgebase/use-301-redirect-vs-302-redirect-15683/)). Search engines may eventually treat long-term 302s as permanent anyway.

**307/308** - Like 302/301 but preserve HTTP method (POST remains POST). Rarely needed for typical SEO work.

### [How Long to Keep Redirects Active](#how-long-to-keep-redirects-active)

Google recommends keeping 301 redirects active for at least one year ([Search Central](https://www.searchenginejournal.com/google-keep-301-redirects-in-place-for-a-year/428998/)). This ensures Google transfers all ranking signals and recrawls links pointing to old URLs.

Keep redirects longer if:

- External sites still link to old URLs
- Old URLs receive referral traffic
- High-value pages with many backlinks

Removing redirects before Google processes them loses the transferred SEO value permanently.

### [Avoid Redirect Chains](#avoid-redirect-chains)

Redirect chains (A → B → C) waste

 and slow page speed ([Gotch SEO](https://www.gotchseo.com/redirect-chains/)). Each hop degrades

, particularly LCP and TTFB.

Google follows up to 5 redirect hops, then aborts ([Hike SEO](https://developers.google.com/search/docs/crawling-indexing/301-redirects#redirect-chains)). Redirect directly to final destination:

Bad:

```
/old → /interim → /final
```

Good:

```
/old → /final
/interim → /final
```

## [Common Patterns](#common-patterns)

### [Domain Migration](#domain-migration)

Express

Vite

H3

```
import express from 'express'

const app = express()

app.use((req, res, next) => {
  const host = req.get('host')
  if (host === 'old-domain.com') {
    return res.redirect(301, \`https://new-domain.com${req.path}\`)
  }
  next()
})
```

```
// server.js for Vite SSR
import express from 'express'

const app = express()

app.use((req, res, next) => {
  const host = req.get('host')
  if (host === 'old-domain.com') {
    return res.redirect(301, \`https://new-domain.com${req.path}\`)
  }
  next()
})
```

```
import { defineEventHandler, getRequestHost, sendRedirect } from 'h3'

export default defineEventHandler((event) => {
  const host = getRequestHost(event)
  if (host === 'old-domain.com') {
    return sendRedirect(event, \`https://new-domain.com${event.path}\`, 301)
  }
})
```

### [URL Structure Changes](#url-structure-changes)

Express

Vite

H3

```
import express from 'express'

const app = express()

app.get('/old', (req, res) => {
  res.redirect(301, '/new')
})

app.get('/blog/:slug', (req, res) => {
  res.redirect(301, \`/articles/${req.params.slug}\`)
})

app.get('/products/:id', (req, res) => {
  res.redirect(301, \`/shop/${req.params.id}\`)
})
```

```
// server.js for Vite SSR
import express from 'express'

const app = express()

app.use((req, res, next) => {
  if (req.path === '/old') {
    return res.redirect(301, '/new')
  }
  if (req.path.startsWith('/blog/')) {
    const slug = req.path.replace('/blog/', '')
    return res.redirect(301, \`/articles/${slug}\`)
  }
  if (req.path.startsWith('/products/')) {
    const id = req.path.replace('/products/', '')
    return res.redirect(301, \`/shop/${id}\`)
  }
  next()
})
```

```
import { defineEventHandler, sendRedirect } from 'h3'

export default defineEventHandler((event) => {
  if (event.path === '/old') {
    return sendRedirect(event, '/new', 301)
  }
  if (event.path.startsWith('/blog/')) {
    const slug = event.path.replace('/blog/', '')
    return sendRedirect(event, \`/articles/${slug}\`, 301)
  }
  if (event.path.startsWith('/products/')) {
    const id = event.path.replace('/products/', '')
    return sendRedirect(event, \`/shop/${id}\`, 301)
  }
})
```

### [HTTPS Enforcement](#https-enforcement)

Express

Vite

H3

```
import express from 'express'

const app = express()

app.use((req, res, next) => {
  if (req.headers['x-forwarded-proto'] !== 'https') {
    return res.redirect(301, \`https://${req.get('host')}${req.path}\`)
  }
  next()
})
```

```
// server.js for Vite SSR
import express from 'express'

const app = express()

app.use((req, res, next) => {
  if (req.headers['x-forwarded-proto'] !== 'https') {
    return res.redirect(301, \`https://${req.get('host')}${req.path}\`)
  }
  next()
})
```

```
import { defineEventHandler, getHeader, getRequestHost, sendRedirect } from 'h3'

export default defineEventHandler((event) => {
  if (getHeader(event, 'x-forwarded-proto') !== 'https') {
    const host = getRequestHost(event)
    return sendRedirect(event, \`https://${host}${event.path}\`, 301)
  }
})
```

Learn more about HTTPS in our

.
### [WWW Standardization](#www-standardization)

Express

Vite

H3

```
import express from 'express'

const app = express()

app.use((req, res, next) => {
  const host = req.get('host')
  if (!host.startsWith('www.')) {
    return res.redirect(301, \`https://www.${host}${req.path}\`)
  }
  next()
})
```

```
// server.js for Vite SSR
import express from 'express'

const app = express()

app.use((req, res, next) => {
  const host = req.get('host')
  if (!host.startsWith('www.')) {
    return res.redirect(301, \`https://www.${host}${req.path}\`)
  }
  next()
})
```

```
import { defineEventHandler, getRequestHost, sendRedirect } from 'h3'

export default defineEventHandler((event) => {
  const host = getRequestHost(event)
  if (!host.startsWith('www.')) {
    return sendRedirect(event, \`https://www.${host}${event.path}\`, 301)
  }
})
```

## [Testing Redirects](#testing-redirects)

Verify redirects work correctly before deploying:

1. **Check status code** - Use browser dev tools Network tab, confirm 301 or 302
2. **Test destination** - Ensure redirect points to correct final URL
3. **Verify no chains** - Confirm single hop to destination
4. **Test trailing slashes** - Check with and without trailing slash
5. **Check query parameters** - Verify parameters carry over if needed

### [Tools](#tools)

- [Google Search Console](https://search.google.com/search-console) - Monitor crawl errors and redirect issues
- Browser Dev Tools Network tab - Check status codes and headers
- [Screaming Frog](https://www.screamingfrog.co.uk/seo-spider/) - Bulk redirect testing and chain detection
- curl - `curl -I https://example.com/old-page` shows redirect headers

## [Common Mistakes](#common-mistakes)

### [Redirecting to Irrelevant Pages](#redirecting-to-irrelevant-pages)

Redirecting deleted pages to your homepage damages SEO and user experience. Google may treat this as a soft 404, ignoring link equity transfer ([Victorious](https://developers.google.com/search/docs/crawling-indexing/soft-404-errors)).

❌ Bad

✅ Good

```
import express from 'express'

const app = express()

// Mass redirects to homepage lose link equity
app.get('/blog/*', (req, res) => res.redirect(301, '/'))
```

```
import express from 'express'

const app = express()

// Redirect to relevant content preserves SEO value
app.get('/blog/vue-tips', (req, res) => res.redirect(301, '/articles/vue-tips'))
app.get('/blog/seo-guide', (req, res) => res.redirect(301, '/articles/seo-guide'))
```

### [Redirect Loops](#redirect-loops)

Circular redirects break your site:

❌ Bad

✅ Good

```
import express from 'express'

const app = express()

// Creates infinite loop
app.get('/page-a', (req, res) => res.redirect(301, '/page-b'))
app.get('/page-b', (req, res) => res.redirect(301, '/page-a'))
```

```
import express from 'express'

const app = express()

// Both redirect to final destination
app.get('/page-a', (req, res) => res.redirect(301, '/final'))
app.get('/page-b', (req, res) => res.redirect(301, '/final'))
```

### [Using Client-Side Redirects for SEO](#using-client-side-redirects-for-seo)

JavaScript redirects don't pass link equity reliably. Search engines may not execute JavaScript before indexing. Always use server-side redirects (301/302 status codes) for SEO purposes.

### [Not Updating Internal Links](#not-updating-internal-links)

Relying on redirects for internal links wastes server resources and slows page speed. Update internal links to point directly to new URLs, keep redirects for external links and old bookmarks.

## [Using Nuxt?](#using-nuxt)

If you're using Nuxt, check out

 which handles much of this automatically.

---

On this page

- [Quick Setup](#quick-setup)
- [301 vs 302 Redirects](#_301-vs-302-redirects)
- [Common Patterns](#common-patterns)
- [Testing Redirects](#testing-redirects)
- [Common Mistakes](#common-mistakes)
- [Using Nuxt?](#using-nuxt)

[GitHub](https://github.com/harlan-zw/nuxt-seo) [ Discord](https://discord.com/invite/275MBUBvgP)

###

-
-

Modules

-
-
-
-
-
-
-
-
-

###

-
-
-

###

Nuxt

-
-
-
-
-

Vue

-
-
-
-
-
-
-
-

###

-
-
-
-
-
-
-
-
-
-

Copyright © 2023-2026 Harlan Wilton - [MIT License](https://github.com/harlan-zw/nuxt-seo/blob/main/license) · [mdream](https://mdream.dev)