Custom Vite SSR for Vue: When to Roll Your Own · Nuxt SEO

[NuxtSEO](https://nuxtseo.com/ "Home")

- [Modules](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction)
- [Tools](https://nuxtseo.com/tools)
- [Pro](https://nuxtseo.com/pro)
- [Learn SEO](https://nuxtseo.com/learn-seo/nuxt) [Releases](https://nuxtseo.com/releases)

[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

[SEO Checklist](https://nuxtseo.com/learn-seo/checklist) [Pre-Launch Warmup](https://nuxtseo.com/learn-seo/pre-launch-warmup) [Backlinks & Authority](https://nuxtseo.com/learn-seo/backlinks)

[Mastering Meta](https://nuxtseo.com/learn-seo/vue/mastering-meta)

- [Titles](https://nuxtseo.com/learn-seo/vue/mastering-meta/titles)
- [Meta Description](https://nuxtseo.com/learn-seo/vue/mastering-meta/descriptions)
- [Social Sharing](https://nuxtseo.com/learn-seo/vue/mastering-meta/social-sharing)
- [Schema.org](https://nuxtseo.com/learn-seo/vue/mastering-meta/schema-org)
- [Migrating vue-meta](https://nuxtseo.com/learn-seo/vue/mastering-meta/migrating-vue-meta)
- [Rich Results](https://nuxtseo.com/learn-seo/vue/mastering-meta/rich-results)
- [Image Alt Text](https://nuxtseo.com/learn-seo/vue/mastering-meta/alt-text)

[ Controlling Crawlers](https://nuxtseo.com/learn-seo/vue/controlling-crawlers)

- [Robots.txt](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt)
- [Sitemaps](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps)
- [Robot Meta Tag](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags)
- [Canonical Link Tag](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls)
- [HTTP Redirects](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects)
- [Duplicate Content](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content)
- [llms.txt](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/llms-txt)

[ SPA SEO](https://nuxtseo.com/learn-seo/vue/spa)

- [Prerendering](https://nuxtseo.com/learn-seo/vue/spa/prerendering)
- [Dynamic Rendering](https://nuxtseo.com/learn-seo/vue/spa/dynamic-rendering)
- [Hydration & SEO](https://nuxtseo.com/learn-seo/vue/spa/hydration)

[ Routes & Rendering](https://nuxtseo.com/learn-seo/vue/routes-and-rendering)

- [URL Structure](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure)
- [Pagination](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination)
- [Trailing Slashes](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes)
- [Query Parameters](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters)
- [Hreflang & i18n](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/i18n)
- [404 Pages](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/404-pages)
- [Dynamic Routes](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/dynamic-routes)
- [Internal Linking](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/internal-linking)
- [Rendering Modes](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering)
- [Programmatic SEO](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/programmatic-seo)
- [Security](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/security)

[ SSR Frameworks](https://nuxtseo.com/learn-seo/vue/ssr-frameworks)

- [Nuxt vs Quasar](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/nuxt-vs-quasar)
- [Custom Vite SSR](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr)
- [VitePress SEO](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vitepress)

[ Launch & Listen](https://nuxtseo.com/learn-seo/vue/launch-and-listen)

- [Getting Indexed](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live)
- [Google Search Console](https://nuxtseo.com/learn-seo/vue/launch-and-listen/search-console)
- [Core Web Vitals](https://nuxtseo.com/learn-seo/vue/launch-and-listen/core-web-vitals)
- [Indexing Issues](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexing-issues)
- [SEO Monitoring](https://nuxtseo.com/learn-seo/vue/launch-and-listen/seo-monitoring)
- [Site Migration](https://nuxtseo.com/learn-seo/vue/launch-and-listen/site-migration)
- [IndexNow](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexnow)
- [Debugging](https://nuxtseo.com/learn-seo/vue/launch-and-listen/debugging)
- [AI Search Optimization](https://nuxtseo.com/learn-seo/vue/launch-and-listen/ai-optimized-content)

1. [Learn SEO for Vue](https://nuxtseo.com/learn-seo)
2.
3. [Ssr Frameworks](https://nuxtseo.com/learn-seo/vue/ssr-frameworks)
4.
5. [Vite Ssr](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr)

# Custom Vite SSR for Vue: When to Roll Your Own

Building server-side rendering with Vite instead of frameworks. When custom SSR makes sense and SEO considerations.

[![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan-zw) Published Dec 17, 2025

What you'll learn

- Custom Vite SSR gives full control but requires building routing, data fetching, and SEO tooling yourself
- Most apps should use Nuxt. custom SSR is for specific use cases (microservices, legacy integration)
- Remember: meta tags must render server-side, and hydration mismatches break SEO

Most Vue apps don't need custom SSR. Nuxt handles it better. But if you're building a highly specific app where framework conventions create more friction than value, Vite's SSR primitives let you build exactly what you need.

## [When Custom Vite SSR Makes Sense](#when-custom-vite-ssr-makes-sense)

[Vite provides built-in SSR support](https://vite.dev/guide/ssr) as a low-level API "meant for library and framework authors." Use it when:

- You're building a framework or library yourself
- Framework conventions conflict with your architecture (microservices, multi-tenant apps, legacy integration)
- You need full control over the SSR pipeline for performance optimization
- Your app is small enough that framework overhead isn't worth it

Don't use it for typical marketing sites, blogs, or e-commerce. Nuxt gives you SSR plus routing, data fetching, and SEO tooling. Custom Vite SSR gives you none of that; you build it all.

[The Vue.js docs are explicit](https://vuejs.org/guide/scaling-up/ssr.html): "If you prefer a higher-level solution that provides a smooth out-of-the-box experience, you should probably give Nuxt.js a try."

## [Basic Vite SSR Setup](#basic-vite-ssr-setup)

Vite SSR requires three entry points:

```
├── index.html
├── server.js          # Node server
├── src/
│   ├── main.ts        # Shared app factory
│   ├── entry-client.ts # Client hydration
│   └── entry-server.ts # SSR rendering
```

### [Shared App Factory](#shared-app-factory)

```
// src/main.ts
import { createSSRApp } from 'vue'
import App from './App.vue'

export function createApp() {
  const app = createSSRApp(App)
  return { app }
}
```

Use `createSSRApp()` instead of `createApp()` for SSR compatibility. This configures Vue for server rendering and client hydration.

### [Server Entry](#server-entry)

```
// src/entry-server.ts
import { renderToString } from 'vue/server-renderer'
import { createApp } from './main'

export async function render(url: string) {
  const { app } = createApp()
  const html = await renderToString(app)
  return { html }
}
```

[`renderToString()`](https://vuejs.org/guide/scaling-up/ssr.html) generates HTML from your Vue app. Google receives this HTML immediately. no JavaScript execution required.

### [Client Entry](#client-entry)

```
// src/entry-client.ts
import { createApp } from './main'

const { app } = createApp()
app.mount('#app')
```

Hydrates the server-rendered HTML. The DOM structure must match exactly or you'll get hydration errors (which break SEO by causing layout shifts).

### [Server Setup](#server-setup)

Express

Hono

```
// server.js
import express from 'express'
import { createServer as createViteServer } from 'vite'

const app = express()

const vite = await createViteServer({
  server: { middlewareMode: true },
  appType: 'custom'
})

app.use(vite.middlewares)

app.get('*', async (req, res) => {
  const { render } = await vite.ssrLoadModule('/src/entry-server.ts')
  const { html } = await render(req.url)

  res.send(\`
    <!DOCTYPE html>
    <html>
      <head>
        <title>My App</title>
      </head>
      <body>
        <div id="app">${html}</div>
        <script type="module" src="/src/entry-client.ts"></script>
      </body>
    </html>
  \`)
})

app.listen(3000)
```

```
// server.ts
import { Hono } from 'hono'
import { createServer as createViteServer } from 'vite'

const app = new Hono()

const vite = await createViteServer({
  server: { middlewareMode: true },
  appType: 'custom'
})

app.use('*', async (c) => {
  const { render } = await vite.ssrLoadModule('/src/entry-server.ts')
  const { html } = await render(c.req.url)

  return c.html(\`
    <!DOCTYPE html>
    <html>
      <head>
        <title>My App</title>
      </head>
      <body>
        <div id="app">${html}</div>
        <script type="module" src="/src/entry-client.ts"></script>
      </body>
    </html>
  \`)
})

export default app
```

`middlewareMode: true` lets your server handle routing. [Vite](https://vite.dev) only handles module transformation and HMR.

## [SEO Considerations for Custom SSR](#seo-considerations-for-custom-ssr)

### [1. Meta Tags Must Render Server-Side](#_1-meta-tags-must-render-server-side)

Default Vue apps render meta tags client-side. Google might not wait for JavaScript.

Install [Unhead](https://unhead.unjs.io/):

```
npm install @unhead/vue
```

Setup requires server context:

```
import { renderSSRHead } from '@unhead/ssr'
// src/entry-server.ts
import { renderToString } from 'vue/server-renderer'
import { createApp } from './main'

export async function render(url: string) {
  const { app, head } = createApp()
  const html = await renderToString(app)
  const { headTags } = await renderSSRHead(head)

  return { html, headTags }
}
```

```
import { createHead } from '@unhead/vue'
// src/main.ts
import { createSSRApp } from 'vue'
import App from './App.vue'

export function createApp() {
  const app = createSSRApp(App)
  const head = createHead()
  app.use(head)
  return { app, head }
}
```

Now meta tags render in the initial HTML:

```
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  title: 'My Page',
  description: 'Renders server-side'
})
</script>
```

### [2. Routing Requires Manual Setup](#_2-routing-requires-manual-setup)

Vite doesn't include routing. Add Vue Router yourself:

```
// src/router.ts
import { createMemoryHistory, createRouter, createWebHistory } from 'vue-router'

export function createAppRouter(isServer: boolean) {
  return createRouter({
    history: isServer ? createMemoryHistory() : createWebHistory(),
    routes: [
      { path: '/', component: () => import('./pages/Home.vue') },
      { path: '/about', component: () => import('./pages/About.vue') }
    ]
  })
}
```

Server uses `createMemoryHistory()` (no URL manipulation). Client uses `createWebHistory()` (browser history API).

Update app factory:

```
import { createHead } from '@unhead/vue'
// src/main.ts
import { createSSRApp } from 'vue'
import App from './App.vue'
import { createAppRouter } from './router'

export function createApp(isServer = false) {
  const app = createSSRApp(App)
  const head = createHead()
  const router = createAppRouter(isServer)

  app.use(head)
  app.use(router)

  return { app, head, router }
}
```

Server entry must wait for routing:

```
import { renderSSRHead } from '@unhead/ssr'
// src/entry-server.ts
import { renderToString } from 'vue/server-renderer'
import { createApp } from './main'

export async function render(url: string) {
  const { app, head, router } = createApp(true)

  await router.push(url)
  await router.isReady()

  const html = await renderToString(app)
  const { headTags } = await renderSSRHead(head)

  return { html, headTags }
}
```

`router.isReady()` waits for async components to load. Without this, Google sees empty content.

### [3. Data Fetching Needs Coordination](#_3-data-fetching-needs-coordination)

No built-in data fetching like Nuxt's `useFetch()`. Options:

**Option A: Route-level data loading**

src/pages/Blog.vue

```
<script setup lang="ts">
import { onServerPrefetch, ref } from 'vue'

const posts = ref([])

async function loadPosts() {
  const res = await fetch('https://api.example.com/posts')
  posts.value = await res.json()
}

onServerPrefetch(async () => {
  await loadPosts()
})
</script>
```

`onServerPrefetch()` runs during SSR, not on client. You need to fetch again client-side or serialize state.

**Option B: State serialization**

```
// src/entry-server.ts
export async function render(url: string) {
  const { app, head, router } = createApp(true)

  await router.push(url)
  await router.isReady()

  // Data loaded during SSR is available here
  const state = { /* extract state */ }

  const html = await renderToString(app)
  const { headTags } = await renderSSRHead(head)

  return { html, headTags, state }
}
```

```
<script>
  window.__INITIAL_STATE__ = ${JSON.stringify(state)}
</script>
```

Client rehydrates from `window.__INITIAL_STATE__`. This prevents duplicate fetches but requires manual state management.

[Nuxt handles this automatically](https://nuxt.com/docs/getting-started/data-fetching). Custom Vite SSR doesn't.

## [Using Vite SSR Plugins](#using-vite-ssr-plugins)

Writing custom SSR is tedious. Plugins reduce boilerplate.

### [Vike](#vike)

[Vike](https://vike.dev/) (formerly vite-plugin-ssr) is "like Next.js/Nuxt but as do-one-thing-do-it-well Vite plugin."

```
npm install vike vike-vue
```

```
// vite.config.ts
import vue from '@vitejs/plugin-vue'
import vike from 'vike/plugin'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [vue(), vike()]
})
```

File-based routing with `+Page.vue` convention:

```
pages/
  index/+Page.vue      # /
  about/+Page.vue      # /about
  blog/
    index/+Page.vue    # /blog
    @slug/+Page.vue    # /blog/:slug
```

Data fetching per page:

```
// blog/@slug/+data.ts
export async function data(pageContext) {
  const { slug } = pageContext.routeParams
  const res = await fetch(\`https://api.example.com/posts/${slug}\`)
  return { post: await res.json() }
}
```

Server-only code. Never sent to client. Better for SEO (less JavaScript).

### [vite-ssr (frandiox)](#vite-ssr-frandiox)

[vite-ssr](https://github.com/frandiox/vite-ssr) integrates with Vue Router and state management:

```
npm install vite-ssr
```

```
// src/main.ts
import { viteSSR } from 'vite-ssr/vue'
import App from './App.vue'
import routes from './routes'

export default viteSSR(App, { routes }, ({ app, router, initialState }) => {
  // Setup runs once on server, once on client
  // initialState syncs automatically
})
```

Handles state serialization and hydration. Supports [Pinia](https://pinia.vuejs.org) by default.

Deploy to serverless (Vercel, [Netlify](https://netlify.com)) or edge workers (Cloudflare).

## [Production Build](#production-build)

Development uses middleware mode. Production needs separate client and server builds.

```
{
  "scripts": {
    "dev": "node server.js",
    "build": "npm run build:client && npm run build:server",
    "build:client": "vite build --outDir dist/client",
    "build:server": "vite build --ssr src/entry-server.ts --outDir dist/server"
  }
}
```

```
import vue from '@vitejs/plugin-vue'
// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [vue()],
  build: {
    rollupOptions: {
      input: {
        main: './index.html'
      }
    }
  }
})
```

Production server imports from `server`:

```
// server.prod.js
import express from 'express'
import { render } from './server/entry-server.js'

const app = express()

app.use(express.static('dist/client'))

app.get('*', async (req, res) => {
  const { html, headTags } = await render(req.url)

  res.send(\`
    <!DOCTYPE html>
    <html>
      <head>
        ${headTags}
      </head>
      <body>
        <div id="app">${html}</div>
        <script type="module" src="/assets/entry-client.js"></script>
      </body>
    </html>
  \`)
})

app.listen(3000)
```

## [Trade-offs vs Frameworks](#trade-offs-vs-frameworks)

**Custom Vite SSR gives you:**

- Full architectural control
- Minimal bundle size (no framework overhead)
- Custom rendering pipeline (streaming, partial hydration)
- Integration flexibility

**You lose:**

- File-based routing (manual setup)
- Data fetching utilities (manual state management)
- SEO tooling (no automatic sitemaps, meta management, schema.org)
- Deployment presets (manual server configuration)
- Developer experience (no conventions)

[The Vue SSR guide warns](https://vuejs.org/guide/scaling-up/ssr.html): "More involved build setup and deployment requirements. Unlike a fully static SPA that you can deploy on any static file server, a server-rendered app requires an environment where a [Node.js](https://nodejs.org) server can run."

If you're building a typical web app, use Nuxt. If you need SSR for a specific use case (embedded widgets, multi-tenant platforms, legacy integration), Vite's primitives let you build exactly what you need.

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

**Using lifecycle hooks incorrectly**

`onMounted()` never runs on server. `onServerPrefetch()` never runs on client. Use the right hook for each environment.

**Hydration mismatches**

Server HTML must match client exactly. Random IDs, timestamps, or client-only rendering breaks hydration:

```
❌ Bad
<script setup lang="ts">
const id = import.meta.client ? Math.random() : 0.5
</script>

<template>
  <div :id="\`item-${id}\`">
    Content
  </div>
</template>

✅ Good
<template>
  <div :id="\`item-${props.id}\`">Content</div>
</template>
```

**Accessing browser APIs during SSR**

`window`, `document`, `localStorage` don't exist on server:

```
// ❌ Bad
const saved = localStorage.getItem('theme')

// ✅ Good
const saved = import.meta.env.SSR
  ? null
  : localStorage.getItem('theme')
```

Or use `onMounted()` which only runs client-side.

**Not testing the production build**

Vite's dev server behaves differently than production. Always test `npm run build && node server.prod.js` before deploying.

## [Verification](#verification)

Test SSR output before deploying:

```
curl http://localhost:3000/blog/my-post
```

Should return full HTML with content. If you see `<div id="app"></div>` with no content, SSR isn't working.

Use [Google's URL Inspection tool](https://search.google.com/search-console) to verify Googlebot sees rendered content.

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

Nuxt handles all of this automatically with file-based routing, built-in data fetching, and SEO utilities. Check out [Nuxt SEO](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction) for production-ready SEO tooling.

[Learn more about Nuxt →](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/rendering)

[The 2026 SEO Checklist for Nuxt & Vue Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.](https://nuxtseo.com/learn-seo/checklist) [Haven't launched yet? Start with the Pre-Launch Warmup](https://nuxtseo.com/learn-seo/pre-launch-warmup)

---

[Nuxt vs Quasar Nuxt dominates for SSR-first SEO. Quasar excels at cross-platform. Compare SSR capabilities, SEO features, and when to choose each.](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/nuxt-vs-quasar) [VitePress SEO VitePress offers built-in SEO features like sitemap generation, meta tags, and fast static site generation for documentation and blogs.](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vitepress)

On this page

- [When Custom Vite SSR Makes Sense](#when-custom-vite-ssr-makes-sense)
- [Basic Vite SSR Setup](#basic-vite-ssr-setup)
- [SEO Considerations for Custom SSR](#seo-considerations-for-custom-ssr)
- [Using Vite SSR Plugins](#using-vite-ssr-plugins)
- [Production Build](#production-build)
- [Trade-offs vs Frameworks](#trade-offs-vs-frameworks)
- [Common Mistakes](#common-mistakes)
- [Verification](#verification)
- [Using Nuxt?](#using-nuxt)

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

### [NuxtSEO](https://nuxtseo.com/ "Home")

- [Getting Started](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction)
- [MCP](https://nuxtseo.com/docs/nuxt-seo/guides/mcp)

Modules

- [Robots](https://nuxtseo.com/docs/robots/getting-started/introduction)
- [Sitemap](https://nuxtseo.com/docs/sitemap/getting-started/introduction)
- [OG Image](https://nuxtseo.com/docs/og-image/getting-started/introduction)
- [Schema.org](https://nuxtseo.com/docs/schema-org/getting-started/introduction)
- [Link Checker](https://nuxtseo.com/docs/link-checker/getting-started/introduction)
- [SEO Utils](https://nuxtseo.com/docs/seo-utils/getting-started/introduction)
- [Site Config](https://nuxtseo.com/docs/site-config/getting-started/introduction)
- [Skew Protection](https://nuxtseo.com/docs/skew-protection/getting-started/introduction)
- [AI Ready](https://nuxtseo.com/docs/ai-ready/getting-started/introduction)

### [NuxtSEO Pro](https://nuxtseo.com/pro "Home")

- [Getting Started](https://nuxtseo.com/pro)
- [Dashboard](https://nuxtseo.com/pro/dashboard)
- [Pro MCP](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation)

### [Learn SEO](https://nuxtseo.com/learn-seo "Learn SEO")

Nuxt

- [Mastering Meta](https://nuxtseo.com/learn-seo/nuxt/mastering-meta)
- [Controlling Crawlers](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers)
- [Launch & Listen](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen)
- [Routes & Rendering](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering)
- [Staying Secure](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/security)

Vue

- [Vue SEO Guide](https://nuxtseo.com/learn-seo/vue)
- [Mastering Meta](https://nuxtseo.com/learn-seo/vue/mastering-meta)
- [Controlling Crawlers](https://nuxtseo.com/learn-seo/vue/controlling-crawlers)
- [SPA SEO](https://nuxtseo.com/learn-seo/vue/spa)
- [SSR Frameworks](https://nuxtseo.com/learn-seo/vue/ssr-frameworks)
- [SEO Checklist](https://nuxtseo.com/learn-seo/checklist)
- [Pre-Launch Warmup](https://nuxtseo.com/learn-seo/pre-launch-warmup)
- [Backlinks & Authority](https://nuxtseo.com/learn-seo/backlinks)

### [Tools](https://nuxtseo.com/tools "SEO Tools")

- [Social Share Debugger](https://nuxtseo.com/tools/social-share-debugger)
- [Robots.txt Generator](https://nuxtseo.com/tools/robots-txt-generator)
- [Meta Tag Checker](https://nuxtseo.com/tools/meta-tag-checker)
- [HTML to Markdown](https://nuxtseo.com/tools/html-to-markdown)
- [XML Sitemap Validator](https://nuxtseo.com/tools/xml-sitemap-validator)
- [Schema.org Validator](https://nuxtseo.com/tools/schema-validator)
- [Keyword Idea Generator](https://nuxtseo.com/tools/keyword-generator)
- [Keyword Research](https://nuxtseo.com/tools/keyword-research)
- [SERP Analyzer](https://nuxtseo.com/tools/serp-analyzer)
- [Domain Rankings](https://nuxtseo.com/tools/domain-rankings)

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