Nuxt SEO Essentials: Rendering, Metadata, Sitemaps and JSON-LD
Author TallmanCode
Categories Vue.js, Nuxt, Typescript, AEO, SEO

Hey there, fellow Nuxt developers! If you've ever wondered how to get your Nuxt application in front of search engines, you're in the right place. We're going to walk through the pieces that matter most: how your pages are rendered, how you describe them with metadata, how crawlers discover them, and how you give search engines extra context with structured data.
A quick note on versions before we start. This article began life as a Nuxt 3 guide, and a few of its original code samples were actually Nuxt 2 patterns. Everything below has been updated for current Nuxt, and it works on Nuxt 3 and Nuxt 4. If you're using Nuxt 4, remember that the default source directory is app/, so paths like pages/ become app/pages/.
Sound good? Let's dive in.
Rendering Modes: Where SEO Begins
Nuxt is flexible when it comes to rendering, and that choice has a real impact on SEO. Search engines do best when they receive fully formed HTML, so it helps to know what each mode sends them.
Universal Rendering (SSR): The Default
Out of the box, Nuxt uses universal rendering. The server renders your page to HTML for each request, and then Vue hydrates it in the browser so it becomes interactive. The Nuxt docs point out that this default exists partly to help search engine indexing.
Because it's the default, you don't need to configure anything. If you like being explicit, though, this is what it looks like:
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true
})This mode is a great fit for content that changes often, since each request gets fresh HTML.
Static Generation (SSG): Prepared in Advance
With static generation, Nuxt pre-renders your pages at build time, so the finished HTML files can be served straight from a CDN. It's a good choice for content that doesn't change much, like marketing pages or documentation.
Here's a detail worth flagging. Older Nuxt 2 guides show a target: 'static' option in the config. That option doesn't exist in Nuxt 3 or 4. Instead, you run a command:
npx nuxt generateKeep in mind that a fully pre-rendered output has no server included, so you can't use server endpoints with it. If you need server functionality, use a regular nuxt build.
Hybrid Rendering: The Best of Both
Here's the cool part. You don't have to pick one mode for your whole app. With routeRules, you can set the rendering strategy per route:
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Pre-render at build time
'/': { prerender: true },
// Server-render with caching (stale-while-revalidate)
'/blog/**': { swr: 3600 },
// Client-side only, no need to be indexed
'/dashboard/**': { ssr: false }
}
})A public blog and a logged-in dashboard have very different needs, and this lets each one have what it wants. Pages that should rank get real HTML, and pages that don't need to be found can skip server rendering entirely.
Not sure what crawlers actually receive? View the page source in your browser, or request the page with curl. With universal rendering, you should see your content in the HTML. With client-side rendering, you'll see a mostly empty shell.
Metadata: Describing Your Pages
Titles and meta descriptions are how search engines and social platforms understand and present your pages. In Nuxt 3 and 4, the go-to tools are useSeoMeta and useHead. The useSeoMeta composable lets you define SEO tags as a flat object with full type safety, which helps you avoid typos and common mistakes like using name where property is needed.
If you've worked with Nuxt 2, you may remember the head() method with a hid key for deduplication. That approach has been replaced, and objects no longer use hid.
Dynamic Metadata with Nuxt Content
Let's put this to work with Nuxt Content. Content v3 uses collections, so first we define one:
// content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
export default defineContentConfig({
collections: {
content: defineCollection({
type: 'page',
source: '**'
})
}
})Then, in a catch-all page, we fetch the document that matches the current route and use its front matter for our metadata:
<!-- pages/[...slug].vue -->
<script setup lang="ts">
const route = useRoute()
const { data: article } = await useAsyncData(route.path, () => {
return queryCollection('content').path(route.path).first()
})
if (!article.value) {
throw createError({
statusCode: 404,
statusMessage: 'Page not found',
fatal: true
})
}
useSeoMeta({
title: article.value.title,
description: article.value.description,
ogTitle: article.value.title,
ogDescription: article.value.description
})
</script>
<template>
<article v-if="article">
<h1>{{ article.title }}</h1>
<ContentRenderer :value="article" />
</article>
</template>If you're coming from older tutorials, a few things changed here. There's no global $content variable anymore, and asyncData with .fetch() belongs to Nuxt 2 and Content v1. In Content v3, you query a specific collection with queryCollection, and you render the result with the ContentRenderer component.
Sitemaps: A Map for Crawlers
A sitemap helps search engines discover your pages, which is especially handy for new sites or pages that aren't linked from many places. The official Nuxt module handles this for you. Install it:
npm install @nuxtjs/sitemapThen add it to your config, along with your site's canonical URL:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/sitemap'],
site: {
url: 'https://yourapp.com'
}
})That site URL matters. At a minimum the module needs it, and without it your sitemap will use localhost in production. If you've seen older guides with a sitemap: { hostname: ... } option, that's the Nuxt 2 version of the module.
Once it's running, your sitemap is available at /sitemap.xml. A few tips as you fine-tune it:
- Google ignores the
<priority>and<changefreq>values, so there's no need to tune them. - Google uses
<lastmod>when it's consistently and verifiably accurate, so only set it to real modification dates. - Reference your sitemap in
robots.txtand submit it in Google Search Console.
If you'd like a bundle, the @nuxtjs/seo package includes the sitemap module along with other Nuxt SEO modules, so you don't need to install both.
Structured Data: Giving Search Engines Context
Structured data uses the Schema.org vocabulary to describe what a page is about. JSON-LD is the format Google recommends, and it lives in a script tag in your page.
Here's a gotcha that trips people up: Vue doesn't allow script tags inside a component's template, so you can't drop a JSON-LD block in there. Instead, add it to the head with useHead. Building on our page from earlier:
<script setup lang="ts">
// ...after fetching `article` as shown above
useHead({
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: article.value.title,
description: article.value.description
})
}
]
})
</script>Add properties like author and datePublished as your content allows. If you'd prefer type safety and automatic linking between schema types, the Nuxt Schema.org module and its useSchemaOrg helper are worth a look.
One honest caveat: Google doesn't guarantee that structured data will produce rich results in search. Your markup needs to match your visible content and follow Google's guidelines. You can check your work with Google's Rich Results Test.
SEO Best Practices Worth Keeping in Mind
The technical setup gets you in the door, but a few fundamentals do a lot of the heavy lifting:
- Content comes first. Write pages that genuinely answer the questions people are searching for.
- Think mobile-first. Google primarily uses the mobile version of your pages for indexing, so make sure your mobile pages have the same content and structured data as desktop. Responsive design is the approach Google recommends.
- Watch your speed. Core Web Vitals measure loading (LCP), responsiveness (INP), and layout stability (CLS). Nuxt gives you a solid start, and tools like PageSpeed Insights and Lighthouse show you where to improve.
- Build accessibly. Semantic HTML, clear heading structure, and useful alt text make your pages better for people, and they give search engines helpful context too.
Putting It to Work in the Real World
Different kinds of sites lean on different tools:
- E-commerce: Prerender or cache your category and product pages, and use Product structured data. Google specifically calls out Product markup as a priority for mobile.
- Blogs: Set solid metadata on every post, and add Article structured data so search engines understand what each page is about.
- Corporate and marketing sites: Static generation works well here, since the content changes rarely and pre-rendered pages load quickly.
Wrapping Up
That's a solid foundation for Nuxt SEO: choose the right rendering mode, describe every page with good metadata, help crawlers with a sitemap, and add structured data where it fits. SEO is always evolving, so it's worth revisiting these settings now and then. Keep building, keep testing what search engines actually see, and until next time!