DomainLens

Guides

Nuxt SEO Checklist: Rendering Modes, Head Management and Routing

Nuxt server-renders by default, which removes most of what goes wrong with Vue and SEO. The remaining work is metadata, canonicals and status codes.

Check your site before you start fixing

Run a fresh DomainLens audit and use the report as your priority list.

Run a free SEO audit

Rendering modes, and choosing per route

Nuxt runs in universal mode by default, rendering on the server and hydrating on the client — the safe choice for anything public. It can also prerender routes at build time, or run as a pure SPA.

Route rules let you mix these in one application, which is the feature worth understanding: marketing pages prerendered, dynamic content server-rendered, and the authenticated app as a client-side bundle.

Per-route rendering in nuxt.config
export default defineNuxtConfig({
  routeRules: {
    // Prerendered at build — fastest, no server work
    '/': { prerender: true },
    '/blog/**': { prerender: true },

    // Server-rendered per request
    '/products/**': { ssr: true },

    // Client-only: never for public content
    '/dashboard/**': { ssr: false },
  },
});

Head management that runs on the server

useHead and useSeoMeta run during server rendering, so the tags they set appear in the initial HTML. That is the correct mechanism and it is the default — the failure mode is calling them inside a client-only component or after an await that only resolves on the client.

Set the canonical explicitly. Nuxt does not add one, and without it every query-string variant of a route competes with the clean URL.

Page metadata including canonical
<script setup>
const route = useRoute();
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`);

useSeoMeta({
  title: post.value.title,
  description: post.value.excerpt,
  ogTitle: post.value.title,
  ogImage: post.value.image,
});

useHead({
  link: [{
    rel: 'canonical',
    href: `https://example.com${route.path}`,
  }],
});
</script>

The checklist

The status-code step is the one most often skipped, and it produces soft 404s at scale exactly as it does in every other framework. See soft 404 errors .

  1. 1Confirm public routes are server-rendered or prerendered — fetch with curl and check the content is there.
  2. 2Set title, description and canonical on every page through useSeoMeta or useHead.
  3. 3Return a real 404 with createError for unmatched routes, rather than rendering an error component with a 200.
  4. 4Generate the sitemap from your content, not by hand.
  5. 5Use NuxtLink for internal navigation; it renders an anchor with an href.
  6. 6Set explicit dimensions on images and avoid lazy-loading the LCP element.

Returning a real 404

createError sets the status
<script setup>
const { data: post } = await useFetch(`/api/posts/${slug}`);

if (!post.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Post not found',
    fatal: true,
  });
}
</script>

<!-- Verify from outside:
     curl -sI https://example.com/blog/nope | head -n 1
     must read 404, not 200 -->

Where Nuxt sites still lose

Hydration mismatch is the subtle one: the page works, the content is right, and the metrics are worse than they should be because the browser rebuilt everything the server already sent.

  • Hydration mismatches, where server and client output disagree and Vue rebuilds the DOM — visible as flicker and unstable metrics.
  • Data fetched only on the client, so the server HTML renders with empty state.
  • Modules that inject head tags after hydration, arriving too late for the first pass.
  • Large hydration payloads that delay interactivity and hurt INP.

How DomainLens contributes

DomainLens reads the served HTML and reports whether the title, canonical, content and links are present before any JavaScript runs — which for a Nuxt app confirms the rendering mode is doing what the config says. See the Vue SPA checklist for the non-Nuxt case.

Is Nuxt SEO-friendly by default?
Largely yes, because universal rendering is the default. You still need to set canonicals and return real 404 statuses, neither of which Nuxt does for you.
Should I use useHead or useSeoMeta?
useSeoMeta for standard meta tags, since it is typed and less error-prone. useHead for anything it does not cover, such as link elements.
How do I return a 404 from a Nuxt page?
Throw createError with statusCode 404 and fatal true. Rendering an error component without it returns 200 and produces a soft 404.
Can I mix rendering modes in one Nuxt app?
Yes, with route rules — prerender marketing pages, server-render dynamic ones, and disable SSR for authenticated routes.

Related resources