Nuxt SEO: Nuxt 3, Nitro, useHead, Server Components
Canonical reference for Nuxt SEO implementation. Nuxt is the Vue ecosystem's full-stack meta-framework, the equivalent of Next.js for React.
Nuxt 3 and Nuxt 4 with Vue 3.5 in 2026, the SEO module ecosystem, useHead and useSeoMeta composables, Nitro server engine, hybrid rendering via routeRules, and the recommended production stack for Joseph's Bubbles-hosted Nuxt deployments.
Canonical reference for Nuxt SEO implementation. Nuxt is the Vue ecosystem's full-stack meta-framework, the equivalent of Next.js for React.
1. Document Purpose
Nuxt is the production Vue framework of choice in 2026. Nuxt 4, released Q2 2025, refined the developer experience introduced in Nuxt 3 (Nitro server, file-based routing, auto-imports) and added the islands architecture by default. Vue 3.5, released late 2024, brought reactive props destructuring, useId, useTemplateRef, and substantial reactivity performance gains.
For SEO, Nuxt requires understanding three core ideas. First, the rendering mode (universal SSR by default, static generation via nuxt generate, hybrid rendering via routeRules, or SPA mode). Second, the metadata API (useHead, useSeoMeta) which is type-safe and reactive. Third, the modules ecosystem (nuxt-schema-org, nuxt-og-image, @nuxtjs/sitemap, @nuxtjs/robots, @nuxt/image, @nuxtjs/i18n) which centralizes SEO functionality.
Nuxt positions against Next.js as the Vue equivalent. The architectural decisions overlap heavily but the developer experience differs. Auto-imports remove boilerplate. The modules system feels more like WordPress plugins than React libraries. Vue's template syntax is closer to HTML than JSX is. Nuxt positions against SvelteKit as a more mature framework with a larger ecosystem. Nuxt positions against Astro as a fuller-featured framework that can build any web application; Astro is content-focused.
This framework specifies Nuxt SEO implementation including rendering strategy decisions, useHead and useSeoMeta patterns, the schema-org module configuration, sitemap and robots module setup, og:image automation, internationalization via @nuxtjs/i18n, and self-hosted deployment on Bubbles.
1.1 Required Tools
- Nuxt 4 (current stable; Nuxt 3 acceptable for existing projects)
- Vue 3.5
- TypeScript (strongly recommended; Nuxt is TypeScript-first)
- pnpm (Nuxt's preferred package manager)
- Node.js 20 or 22 LTS
- PM2 for production process management
- Nginx as the reverse proxy on Bubbles
2. Client Variables Intake
client_nuxt_intake:
project_basics:
domain: "example.com"
nuxt_version: "4.x or 3.x"
vue_version: "3.5.x"
typescript: true
package_manager: "pnpm | npm | yarn | bun"
node_version: "20.x or 22.x LTS"
rendering_strategy:
primary_mode: "universal_ssr | static_ssg | hybrid_route_rules | spa"
nitro_preset: "node-server | static | bun | deno"
pre_render_targets:
- "/"
- "/about"
- "/services"
- "/blog/**"
content_source:
type: "nuxt_content | headless_cms | api | static_files"
cms_name: "if applicable: Sanity, Strapi, Storyblok, Contentful"
nuxt_content_version: "v2.x or v3.x if used"
seo_module_stack:
nuxt_seo_meta_module: "installed | not_installed"
sitemap_module: "@nuxtjs/sitemap installed | custom | none"
robots_module: "@nuxtjs/robots installed | custom | none"
schema_org_module: "nuxt-schema-org installed | custom | none"
og_image_module: "nuxt-og-image installed | custom | none"
image_module: "@nuxt/image installed | not_installed"
i18n_module: "@nuxtjs/i18n installed | not_installed"
international:
locales: ["en", "es", "de", "fr"]
strategy: "no_prefix | prefix_except_default | prefix | prefix_and_default"
default_locale: "en"
hreflang_required: true
hosting:
platform: "bubbles_self_hosted | other"
bubbles_path: "/var/www/sites/[domain]/"
pm2_process_name: "nuxt-[domain]"
nginx_upstream_port: "3000"
domain_in_nginx: "example.com"
performance_targets:
lighthouse_performance: "minimum 90"
inp_p75: "under 200ms"
lcp_p75: "under 2.5s"
cls_p75: "under 0.1"
js_budget_kb: "under 150 KB initial"
schema_requirements:
organization_schema: true
article_schema_on_blog: true
product_schema_if_ecommerce: false
local_business_if_applicable: true
breadcrumb_schema: true
faq_schema_where_relevant: true
3. Nuxt Platform Overview 2026
Nuxt 4 reached general availability in Q2 2025. The release consolidated and refined the developer experience that Nuxt 3 introduced (full TypeScript, Vite by default, Nitro server, Vue 3 reactivity). Nuxt 4 added the islands architecture as a first-class feature, improved auto-imports performance, and stabilized the experimental features that had landed in late Nuxt 3 minor releases.
Vue 3.5, released in September 2024, brought reactive props destructuring (destructuring no longer breaks reactivity), useId for stable SSR IDs, useTemplateRef for cleaner refs, and a substantial reduction in reactivity overhead. Nuxt 4 requires Vue 3.5 or newer.
3.1 The Auto-Imports System
Nuxt auto-imports Vue composables, Nuxt composables, components in the components directory, and utilities in the utils and composables directories. This means you do not write import statements for ref, computed, useHead, useSeoMeta, useFetch, useRoute, or anything else Nuxt provides. The developer experience approaches that of a sealed framework where common operations require zero ceremony.
<script setup lang="ts">
// No imports needed. Nuxt auto-imports these.
const route = useRoute();
const { data } = await useFetch('/api/posts');
useHead({
title: 'Page Title'
});
</script>
This is the single biggest workflow difference versus Next.js. Where Next.js asks you to import everything explicitly, Nuxt asks you to learn the auto-import conventions and trust them.
3.2 File-Based Routing
Nuxt's file-based routing lives in the pages directory. Each file becomes a route. Dynamic segments use bracket notation. Catch-all routes use spread bracket notation. Nested routes use directories.
nuxt_routing_examples:
"pages/index.vue": "/"
"pages/about.vue": "/about"
"pages/blog/index.vue": "/blog"
"pages/blog/[slug].vue": "/blog/any-slug-here"
"pages/blog/category/[category]/[slug].vue": "/blog/category/news/post-title"
"pages/[...slug].vue": "/anything/nested/here (catch-all)"
"pages/users-[group]/[id].vue": "/users-admin/42 (modifier in segment)"
Routing is enabled by including a pages directory in the project. Without pages, Nuxt operates as a single-page application with app.vue as the root component, which is occasionally useful for simple landing pages but rarely the right choice for SEO.
3.3 The Nitro Server Engine
Nitro is Nuxt's server engine. It compiles the application to a deployable artifact and provides the HTTP server, the API layer (server/api directory), and the rendering pipeline. Nitro supports multiple deployment targets via presets: node-server (default for self-hosted), static (for nuxt generate output), bun, deno, and various third-party platforms.
For Joseph's Bubbles deployments, the node-server preset is the right choice for any site that needs SSR. For sites that can be fully pre-rendered (most marketing sites, documentation, blogs), the static preset via nuxt generate produces a directory of HTML files that nginx serves directly without Node.js running.
3.4 The Modules Ecosystem
Nuxt modules are first-party plugins that extend Nuxt. The official module marketplace at nuxt.com/modules lists hundreds of modules covering analytics, authentication, content management, image optimization, internationalization, SEO, testing, and UI libraries. The SEO-relevant modules form the production stack covered in Section 11.
nuxt_module_install_pattern:
step_1: "Add the module to package.json via the CLI"
command: "pnpm dlx nuxi@latest module add @nuxtjs/sitemap"
step_2: "Configure in nuxt.config.ts modules array"
step_3: "Configure module options in nuxt.config.ts"
step_4: "Use the module's composables, components, or auto-generated routes"
The modules system gives Nuxt its "WordPress for Vue" feel: you install a plugin for sitemaps, another for schema, another for og:images, and they integrate cleanly without ceremony.
4. Rendering Modes
Nuxt's rendering modes determine where HTML is produced and when. The default is universal SSR. Each mode has SEO implications.
4.1 Universal SSR (Default)
The default Nuxt mode renders pages on the server for the initial request, then hydrates on the client for subsequent navigation. The first byte to the browser contains complete HTML. Search engine crawlers, social previews, and AI agents all see the rendered content.
export default defineNuxtConfig({
ssr: true,
nitro: { preset: 'node-server' }
});
For Bubbles deployments, the build output runs as a Node.js process under PM2. Nginx reverse-proxies port 80 and 443 to the Node process. Universal SSR is right for sites with dynamic content, user-specific responses, or content that updates frequently. The SEO consideration: universal SSR ships JavaScript for hydration. Nuxt 4's islands architecture (Section 4.5) reduces this cost dramatically.
4.2 Static Site Generation via nuxt generate
For sites whose content is static or updates on a build schedule, nuxt generate pre-renders every page to HTML at build time. No Node.js process needed in production.
export default defineNuxtConfig({
nitro: {
preset: 'static',
prerender: { routes: ['/', '/about', '/services'], crawlLinks: true }
}
});
pnpm nuxt generate
rsync -av --delete .output/public/ /var/www/sites/example.com/
The crawlLinks option tells Nitro to crawl rendered HTML for links and pre-render them. For very large sites, enumerate routes explicitly via the prerender hook to avoid crawler runaway. SEO implication: static generation is the safest SEO mode. Every page is a static HTML file. Performance is maximized. Trade-off: content updates require rebuild and redeploy.
4.3 Hybrid Rendering via routeRules
Nuxt's most powerful feature for SEO is routeRules, which let you assign different rendering modes per route pattern. Marketing pages pre-rendered, blog posts with ISR, dashboard SSR, admin SPA, all in one application.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/about': { prerender: true },
'/services/**': { prerender: true },
'/blog/**': { isr: 3600 },
'/products/**': { swr: 600 },
'/dashboard/**': { ssr: true },
'/admin/**': { ssr: false },
'/api/**': { cors: true, headers: { 'Cache-Control': 'no-cache' } }
}
});
The rules:
- prerender: build-time static generation
- isr: incremental static regeneration (regenerates after N seconds on next request)
- swr: stale-while-revalidate caching
- ssr: server-side render on each request
- ssr: false: render as client-side SPA
For a typical Joseph site with marketing pages, a blog, and a contact form, the routeRules pattern is: prerender everything marketing, isr the blog with a 30-minute revalidation window, ssr the contact form submission endpoint.
4.4 SPA Mode
For applications that do not benefit from SSR (internal tools, dashboards, anything behind authentication), Nuxt can ship as a single-page application. SEO is poor in this mode by definition; SPA mode is not the right choice for public marketing content.
// nuxt.config.ts
export default defineNuxtConfig({
ssr: false
});
The default app.vue renders client-side. The first byte to the browser is a near-empty HTML shell. JavaScript executes to render the application. Search engines may or may not see the content depending on JavaScript execution support.
For mixed sites, prefer the routeRules approach with ssr: false on specific admin routes rather than globally disabling SSR.
4.5 The Islands Architecture in Nuxt 4
Nuxt 4 introduced the islands architecture as a stable feature. An island is a Vue component that renders on the server, ships its HTML to the client, and does not hydrate. Static islands have zero JavaScript runtime cost. Interactive islands hydrate selectively when the user interacts.
<template>
<main>
<HeroSection />
<NuxtIsland name="ProductGrid" :lazy="true" />
<NuxtIsland name="Testimonials" />
<Footer />
</main>
</template>
The component at components/islands/ProductGrid.vue runs on the server, sends HTML, and does not hydrate. SEO implication: islands architecture is the largest single performance lever in Nuxt 4. A typical marketing site shipping every component as an island reduces JavaScript shipped from 200 KB to 30 KB. Lighthouse Performance jumps from 80 to 95+. INP drops by 100ms or more.
4.6 Streaming Patterns
Nuxt 4 supports streaming responses for SSR routes. The server sends HTML in chunks as it becomes available rather than buffering the full response.
<script setup lang="ts">
const { data: hero } = await useFetch('/api/hero');
const { data: comments } = useLazyFetch('/api/comments');
</script>
<template>
<main>
<Hero :data="hero" />
<ContentSection />
<ClientOnly>
<CommentsSection :comments="comments" />
</ClientOnly>
</main>
</template>
useLazyFetch returns immediately with null data; the actual fetch happens client-side. Good for non-critical content; bad for primary content that crawlers need to see.
5. SEO Implementation
Nuxt's metadata API consists of useHead (general head management) and useSeoMeta (type-safe SEO meta tags). Both are reactive Vue composables. Both are auto-imported. Both work in any rendering mode.
5.1 useHead for Per-Page Meta
useHead manages any head tag. It accepts an object describing title, meta tags, links, scripts, and more. The composable is reactive; passing a ref or computed value updates the head when the value changes.
<script setup lang="ts">
const route = useRoute();
useHead({
title: 'About ThatDeveloperGuy',
meta: [
{ name: 'description', content: 'Service-Disabled Veteran-Owned Small Business based in Cassville, Missouri.' },
{ name: 'robots', content: 'index, follow, max-image-preview:large, max-snippet:-1' }
],
link: [
{ rel: 'canonical', href: `https://thatdeveloperguy.com${route.path}` }
]
});
</script>
<template>
<main>
<h1>About ThatDeveloperGuy</h1>
</main>
</template>
The page title appears in the browser tab and in search engine result snippets. The description appears in result snippets. The robots tag tells search engines how to treat the page. The canonical link tells search engines the preferred URL for the content.
5.2 useSeoMeta for Type-Safe OG and Twitter
useSeoMeta is a typed composable for SEO-specific meta tags. It accepts a flat object with named properties for title, description, ogTitle, ogDescription, ogImage, twitterCard, and dozens of other tags. The benefit over useHead is type safety: TypeScript catches typos and provides autocomplete.
<script setup lang="ts">
const route = useRoute();
const post = await queryContent('/blog').where({ _path: route.path }).findOne();
useSeoMeta({
title: post.title,
description: post.description,
ogTitle: post.title,
ogDescription: post.description,
ogImage: post.ogImage || 'https://thatdeveloperguy.com/og-default.jpg',
ogUrl: `https://thatdeveloperguy.com${route.path}`,
ogType: 'article',
ogLocale: 'en_US',
ogSiteName: 'ThatDeveloperGuy',
articleAuthor: ['Joseph W. Anady'],
articlePublishedTime: post.publishedAt,
articleModifiedTime: post.updatedAt,
twitterCard: 'summary_large_image',
twitterTitle: post.title,
twitterDescription: post.description,
twitterImage: post.ogImage,
twitterCreator: '@thatdeveloperguy'
});
</script>
The composable converts the named properties to the correct meta tag syntax. ogTitle becomes <meta property="og:title" content="...">. twitterCard becomes <meta name="twitter:card" content="...">. articlePublishedTime becomes <meta property="article:published_time" content="...">.
5.3 Sitewide Defaults in nuxt.config.ts
Defaults that apply to every page belong in nuxt.config.ts under app.head.
export default defineNuxtConfig({
app: {
head: {
htmlAttrs: { lang: 'en' },
titleTemplate: '%s | ThatDeveloperGuy',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ name: 'theme-color', content: '#0a3d62' },
{ property: 'og:site_name', content: 'ThatDeveloperGuy' },
{ property: 'og:type', content: 'website' }
],
link: [
{ rel: 'icon', type: 'image/png', href: '/favicon.png' }
]
}
}
});
The titleTemplate uses %s as the placeholder. A page that sets useHead({ title: 'About' }) produces "About | ThatDeveloperGuy" via template merge.
5.4 Canonical URL Handling via useRoute
Lift the canonical pattern into a composable to apply across every page.
// composables/useCanonical.ts
export function useCanonical(overridePath?: string) {
const route = useRoute();
const origin = useRuntimeConfig().public.siteUrl;
const path = overridePath ?? route.path;
useHead({
link: [{ rel: 'canonical', href: `${origin}${path}` }]
});
}
Any page calls useCanonical() to set the canonical to the current path. Pages with query strings that should not appear in the canonical pass the cleaned path explicitly.
5.5 The og:image Generation via nuxt-og-image
The nuxt-og-image module generates Open Graph images dynamically at build time or on demand. Per-page og:images dramatically improve social click-through rates and AI search citation visibility.
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-og-image'],
site: {
url: 'https://thatdeveloperguy.com'
},
ogImage: {
fonts: ['Inter:400', 'Inter:700']
}
});
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const post = await queryContent('/blog').where({ _path: route.path }).findOne();
defineOgImageComponent('BlogPost', {
title: post.title,
author: post.author,
date: post.publishedAt
});
</script>
The module pre-renders the og:image as a PNG at build time. The image URL ends up in the og:image meta tag automatically. Each blog post gets a unique branded image with the post title, author, and date overlaid on a template you define in components/OgImage/BlogPost.vue.
Cross-reference: framework-imageseo.md for og:image dimensions, format choices, and the broader image SEO strategy.
6. Schema Implementation
The nuxt-schema-org module, maintained by Harlan Wilton (the same author behind nuxt-og-image, nuxt-seo, and the Nuxt SEO meta-module), provides type-safe Schema.org markup with automatic @id graph generation. The module handles the boilerplate that Next.js sites have to write manually.
6.1 Installation and Sitewide Configuration
pnpm dlx nuxi@latest module add nuxt-schema-org
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-schema-org'],
site: {
url: 'https://thatdeveloperguy.com',
name: 'ThatDeveloperGuy',
description: 'Service-Disabled Veteran-Owned Small Business providing web development and SEO from Cassville, Missouri.',
defaultLocale: 'en'
}
});
<!-- app.vue -->
<script setup lang="ts">
useSchemaOrg([
defineOrganization({
name: 'ThatDeveloperGuy',
logo: '/logo.png',
sameAs: [
'https://www.facebook.com/thatdeveloperguy',
'https://github.com/thatdeveloperguy'
]
}),
defineWebSite({
name: 'ThatDeveloperGuy'
}),
defineWebPage()
])
</script>
The module produces JSON-LD with the Organization, WebSite, and WebPage entities all linked via @id references. The graph appears in the rendered HTML inside a <script type="application/ld+json"> tag.
6.2 Per-Page Schema for Articles
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const post = await queryContent('/blog').where({ _path: route.path }).findOne();
useSchemaOrg([
defineArticle({
headline: post.title,
description: post.description,
image: post.image,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: defineAuthor({
name: post.author,
url: 'https://thatdeveloperguy.com/about/'
})
})
]);
</script>
The module auto-wires the Article into the existing graph. The Article references the WebPage via @id. The WebPage references the WebSite. The Article author references a Person entity. This is the @id graph pattern that Google's structured data documentation recommends.
6.3 LocalBusiness Schema
<script setup lang="ts">
useSchemaOrg([
defineLocalBusiness({
name: 'ThatDeveloperGuy',
image: '/logo.png',
priceRange: '$$',
address: {
streetAddress: '123 Main Street',
addressLocality: 'Cassville',
addressRegion: 'MO',
postalCode: '65625',
addressCountry: 'US'
},
geo: { latitude: 36.6789, longitude: -93.8674 },
telephone: '+1-417-598-8753',
openingHoursSpecification: [
{
dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
opens: '08:00',
closes: '17:00'
}
]
})
]);
</script>
6.4 Product Schema for E-Commerce
<script setup lang="ts">
const route = useRoute();
const product = await $fetch(`/api/products/${route.params.slug}`);
useSchemaOrg([
defineProduct({
name: product.name,
image: product.images,
description: product.description,
sku: product.sku,
brand: defineBrand({ name: product.brand }),
offers: {
price: product.price,
priceCurrency: 'USD',
availability: product.inStock ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock'
}
})
]);
</script>
6.5 Breadcrumb Schema
<script setup lang="ts">
useSchemaOrg([
defineBreadcrumb({
itemListElement: [
{ name: 'Home', item: '/' },
{ name: 'Blog', item: '/blog' },
{ name: post.title }
]
})
]);
</script>
The module fills in absolute URLs and position numbers automatically. Cross-reference: framework-schema.md for the complete schema specification.
7. Performance Profile
Nuxt 4's performance profile in 2026 is competitive with the best frameworks. The combination of the islands architecture, Nitro's output, the @nuxt/image module, and auto-imports produces production sites that score 90+ on Lighthouse Performance with little tuning.
7.1 The Islands Architecture Performance Win
Pre-Nuxt-4 Vue applications shipped hydration JavaScript for every component on the page. Nuxt 4 islands ship HTML for static components and JavaScript only for interactive ones.
Sample measurements (Nuxt team blog, January 2026, n=50 sites migrated Nuxt 3 to Nuxt 4 with islands):
- Total JavaScript shipped: median dropped from 215 KB to 48 KB
- Time to Interactive: median dropped from 3.2 seconds to 1.4 seconds
- Lighthouse Performance: median rose from 78 to 94
- INP p75: median dropped from 180 ms to 95 ms
Source: Nuxt team blog, January 2026, n=50.
7.2 The Nitro Pre-Rendering Pipeline
For statically-generatable content, Nitro pre-renders at build time and serves HTML directly. No per-request work. TTFB is limited only by network distance to origin.
For Bubbles deployments, nuxt generate output behind nginx delivers TTFB under 30ms from LAN, under 100ms from regional visitors. LCP under 1.5 seconds is achievable. CLS near zero with proper image dimensions.
7.3 The @nuxt/image Module
The @nuxt/image module provides <NuxtImg> that automatically generates srcsets, lazy-loads below-fold images, serves modern formats (AVIF, WebP), and handles responsive sizing.
export default defineNuxtConfig({
modules: ['@nuxt/image'],
image: {
provider: 'ipx',
quality: 80,
format: ['avif', 'webp', 'jpg'],
screens: { xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280, '2xl': 1536 }
}
});
<template>
<NuxtImg
src="/images/hero.jpg"
alt="Hero description"
width="1600"
height="900"
sizes="100vw md:50vw lg:33vw"
format="avif"
loading="eager"
fetchpriority="high"
/>
</template>
For below-fold images, omit eager loading and high fetch priority. For Bubbles deployments, the ipx provider runs server-side image processing as part of the Nitro server. Cross-reference: framework-imageseo.md and framework-pageexperience.md.
7.4 Bundle Size Discipline
export default defineNuxtConfig({
experimental: { payloadExtraction: true },
build: { analyze: true }
});
pnpm nuxt analyze
Watch for accidentally importing large libraries into client-side code. Common offenders: date libraries (moment.js is 70+ KB; prefer date-fns or Intl APIs), full UI frameworks (importing all of Material UI when you use three components), chart libraries (lazy-load via dynamic import).
8. URL Structure and Routing
Nuxt's routing is Vue Router based with file-based conventions (Section 3.2). SEO-relevant URL structure decisions are independent of Nuxt and described in framework-technicalseo.md. Nuxt-specific implementation patterns below.
8.1 Dynamic Routes with [param]
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const post = await queryContent('/blog').where({ _path: route.path }).findOne();
if (!post) {
throw createError({ statusCode: 404, statusMessage: 'Not found', fatal: true });
}
</script>
The createError call with fatal: true returns HTTP 404. Critical for SEO: missing content must return 404 not 200 with a "not found" page, or search engines treat URLs as valid and index empty pages.
8.2 Catch-All Routes with [...slug]
<!-- pages/[...slug].vue -->
<script setup lang="ts">
const route = useRoute();
const slugArray = route.params.slug as string[];
const path = '/' + slugArray.join('/');
const page = await queryContent(path).findOne();
if (!page) throw createError({ statusCode: 404, fatal: true });
useSeoMeta({ title: page.title, description: page.description });
</script>
<template>
<ContentRenderer :value="page" />
</template>
Useful for content management systems where URL structure mirrors directory structure.
8.3 The Layouts System
Layouts wrap pages with persistent UI. The default layout lives at layouts/default.vue. Pages opt into layouts via definePageMeta.
<!-- layouts/default.vue -->
<template>
<div>
<SiteHeader />
<slot />
<SiteFooter />
</div>
</template>
<script setup lang="ts">
definePageMeta({ layout: 'landing' });
</script>
SEO consideration: layouts run on every page. Place Organization schema, navigation breadcrumb structure, and footer links once in the layout rather than repeating per-page.
8.4 The Middleware System
Middleware runs before route navigation. Use cases: authentication checks, locale redirects, legacy URL redirects.
// middleware/redirect-legacy.global.ts
export default defineNuxtRouteMiddleware((to) => {
const redirects: Record<string, string> = {
'/old-services-page': '/services',
'/blog/2023-archive': '/blog',
'/contact-us': '/contact'
};
const redirectTo = redirects[to.path];
if (redirectTo) return navigateTo(redirectTo, { redirectCode: 301 });
});
The redirectCode: 301 produces a permanent redirect. Use 302 only for genuinely temporary redirects.
For redirects at scale (hundreds or thousands of legacy URLs after a migration), handle redirects at the nginx level rather than Nuxt middleware. Nginx-level is faster (no Node round-trip) and runs even when Nuxt is restarting.
location = /old-services-page { return 301 /services; }
location = /blog/2023-archive { return 301 /blog; }
Cross-reference: framework-migration.md for redirect strategy during platform migrations.
9. Internationalization with @nuxtjs/i18n
The @nuxtjs/i18n module is the canonical Vue i18n solution. It handles locale detection, URL strategy, hreflang generation, translated messages, and SEO mode.
9.1 Installation and Basic Configuration
pnpm dlx nuxi@latest module add @nuxtjs/i18n
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
locales: [
{ code: 'en', iso: 'en-US', name: 'English', file: 'en.json' },
{ code: 'es', iso: 'es-ES', name: 'Espanol', file: 'es.json' },
{ code: 'de', iso: 'de-DE', name: 'Deutsch', file: 'de.json' },
{ code: 'fr', iso: 'fr-FR', name: 'Francais', file: 'fr.json' }
],
defaultLocale: 'en',
strategy: 'prefix_except_default',
detectBrowserLanguage: { useCookie: true, redirectOn: 'root' },
lazy: true,
langDir: 'locales/',
baseUrl: 'https://example.com'
}
});
9.2 The Strategy Options
The strategy option determines how locales appear in URLs. Each strategy has different SEO implications.
i18n_strategies:
no_prefix:
url_pattern: "/about (regardless of locale)"
seo: "Poor. Hreflang impossible. Avoid for SEO sites."
prefix_except_default:
url_pattern: "/about (default); /es/about, /de/about, /fr/about (others)"
seo: "Best when one locale dominates. Recommended default."
prefix:
url_pattern: "/en/about, /es/about, /de/about, /fr/about"
seo: "Strongest. Every locale equally weighted. Recommended when all locales are first-class."
prefix_and_default:
url_pattern: "/about and /en/about both serve default content"
seo: "Risk of duplicate content. Requires canonical strategy. Rarely correct."
For Joseph's typical clients (single dominant locale with secondary locales), prefix_except_default is correct. For multinational sites where all locales are first-class, prefix is correct.
9.3 Automatic Hreflang Generation
The module automatically generates hreflang tags when SEO mode is enabled. Hreflang is the most error-prone international SEO control because every alternate URL must reference every other alternate URL, codes must use iso 639 plus iso 3166 format, and x-default must be set correctly.
i18n: {
seo: true,
baseUrl: 'https://example.com'
}
The rendered HTML for /about produces:
<link rel="alternate" hreflang="en-US" href="https://example.com/about" />
<link rel="alternate" hreflang="es-ES" href="https://example.com/es/about" />
<link rel="alternate" hreflang="de-DE" href="https://example.com/de/about" />
<link rel="alternate" hreflang="fr-FR" href="https://example.com/fr/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/about" />
Hreflang tags appear on every page in every locale. Every locale references every other locale. The x-default points to the default locale's URL. This is the correct structure Google's documentation requires.
For sites with locale-specific URLs that do not follow a uniform pattern (an "About" page localized as "Sobre" in Spanish at /es/sobre rather than /es/about), use the module's pages configuration:
i18n: {
pages: {
about: { en: '/about', es: '/sobre', de: '/uber-uns', fr: '/a-propos' }
}
}
Cross-reference: framework-hreflang.md for the deep hreflang specification. framework-international.md for international SEO strategy.
9.4 SEO Meta in Translated Pages
<script setup lang="ts">
const { t, locale } = useI18n();
useSeoMeta({
title: t('seo.about.title'),
description: t('seo.about.description'),
ogLocale: locale.value === 'en' ? 'en_US' : locale.value === 'es' ? 'es_ES' : locale.value === 'de' ? 'de_DE' : 'fr_FR'
});
</script>
Translate title, description, structured data textual content, image alt text, and schema descriptions.
10. Nuxt Content
Nuxt Content is the official content management module for Nuxt. It provides file-based content (Markdown, JSON, YAML, CSV) with a queryable API. For content-driven sites with a few hundred pages and a small editorial team, Nuxt Content is often the right choice in place of a headless CMS.
10.1 Installation and Setup
pnpm dlx nuxi@latest module add @nuxt/content
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content'],
content: {
documentDriven: true,
markdown: {
anchorLinks: true,
toc: { depth: 3 }
}
}
});
---
title: How to Build a Nuxt Site for SEO
description: A practical guide to Nuxt SEO patterns.
publishedAt: 2026-05-01
updatedAt: 2026-05-10
author: Joseph W. Anady
image: /og/nuxt-seo.jpg
tags:
- nuxt
- seo
---
# How to Build a Nuxt Site for SEO
The content begins here. Nuxt Content parses the frontmatter and the markdown body.
The markdown file at content/blog/nuxt-seo.md becomes a content document. The frontmatter populates the document properties. The markdown body becomes the rendered content.
10.2 The queryContent Composable
The queryContent composable provides a fluent API for querying content. The composable runs in pages and components, returning content metadata that you use in useSeoMeta and useSchemaOrg calls.
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const post = await queryContent('/blog')
.where({ _path: route.path })
.findOne();
if (!post) {
throw createError({ statusCode: 404, fatal: true });
}
useSeoMeta({
title: post.title,
description: post.description,
ogImage: post.image,
articlePublishedTime: post.publishedAt,
articleModifiedTime: post.updatedAt,
articleAuthor: [post.author]
});
useSchemaOrg([
defineArticle({
headline: post.title,
description: post.description,
image: post.image,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: defineAuthor({ name: post.author })
})
]);
</script>
<template>
<article>
<ContentRenderer :value="post" />
</article>
</template>
The ContentRenderer component renders the markdown body. For blog index pages, query all blog content sorted by date.
<!-- pages/blog/index.vue -->
<script setup lang="ts">
const posts = await queryContent('/blog')
.where({ _draft: { $ne: true } })
.sort({ publishedAt: -1 })
.find();
</script>
<template>
<main>
<h1>Blog</h1>
<ul>
<li v-for="post in posts" :key="post._path">
<NuxtLink :to="post._path">
<h2>{{ post.title }}</h2>
<p>{{ post.description }}</p>
</NuxtLink>
</li>
</ul>
</main>
</template>
10.3 SEO Implications of Nuxt Content
Nuxt Content is fully compatible with static generation. The queryContent calls execute at build time when nuxt generate runs. The rendered HTML contains the full content. Search engines and AI agents see the same HTML that users see.
For content-driven sites (blogs, documentation, marketing sites with a few hundred pages), Nuxt Content + nuxt generate produces a static site with the editorial workflow of a CMS. The content lives in git as markdown files. Edits go through version control. Deploys happen on push. No database, no admin interface, no Node.js process running in production.
The limitations of Nuxt Content versus a headless CMS:
nuxt_content_limits:
editorial_workflow:
description: "Content edits happen in git, not a friendly admin interface"
workaround: "Pair with a CMS-style git GUI like Decap CMS for client editing"
content_count:
description: "Performance degrades past several thousand documents"
workaround: "Use a headless CMS for larger content libraries"
asset_management:
description: "Images and files live in the project's public directory"
workaround: "Hand off image upload to a separate workflow"
workflow:
description: "No draft preview, no scheduled publishing, no editorial roles"
workaround: "Use a headless CMS for editorial-heavy operations"
For sites where the content editor is the developer or where editorial volume is low, Nuxt Content is the right choice. For sites with non-technical editors, multiple authors, or complex editorial workflows, a headless CMS is the right choice.
Cross-reference: framework-headless.md for the headless CMS comparison including Sanity, Strapi, Storyblok, and Contentful integration patterns with Nuxt.
11. Nuxt Modules SEO Stack
The recommended production SEO module stack for Nuxt in 2026:
11.1 Core SEO Modules
nuxt_seo_stack_2026:
"@nuxtjs/seo":
purpose: "All-in-one meta-module bundling sitemap, robots, schema-org, og-image, link-checker, SEO defaults"
when_to_use: "New projects; bundles everything"
"@nuxtjs/sitemap":
purpose: "Automatic sitemap.xml generation"
when_to_use: "Every SEO project"
"@nuxtjs/robots":
purpose: "Automatic robots.txt generation"
when_to_use: "Every SEO project"
"nuxt-schema-org":
purpose: "Type-safe JSON-LD schema with @id graph"
when_to_use: "Every SEO project"
"nuxt-og-image":
purpose: "Dynamic og:image generation per page"
when_to_use: "Sites with social sharing or AI search citations"
"@nuxt/image":
purpose: "Responsive images, format conversion, lazy loading"
when_to_use: "Every site with images"
"@nuxtjs/i18n":
purpose: "Internationalization with automatic hreflang"
when_to_use: "Multilingual sites only"
Install via pnpm dlx nuxi@latest module add [package-name].
11.2 The All-in-One @nuxtjs/seo Meta-Module
For new projects, the @nuxtjs/seo meta-module installs and configures the entire SEO stack at once. The module is maintained by Harlan Wilton and represents the recommended approach in the Nuxt ecosystem.
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/seo'],
site: {
url: 'https://thatdeveloperguy.com',
name: 'ThatDeveloperGuy',
description: 'Service-Disabled Veteran-Owned Small Business based in Cassville, Missouri.',
defaultLocale: 'en',
indexable: true
}
});
This configuration installs and configures @nuxtjs/sitemap (sitemap.xml at /sitemap.xml), @nuxtjs/robots (robots.txt at /robots.txt), nuxt-schema-org (sitewide schema), nuxt-og-image (per-page og:images), nuxt-seo-utils (additional SEO defaults), and nuxt-link-checker (broken link detection during dev). The result is a Nuxt project with comprehensive SEO functionality and minimal configuration overhead.
11.3 The Sitemap Module Configuration
// nuxt.config.ts
export default defineNuxtConfig({
sitemap: {
sources: ['/api/__sitemap__/urls'],
exclude: ['/admin/**', '/api/**', '/preview/**'],
defaults: { changefreq: 'weekly', priority: 0.8, lastmod: new Date() }
}
});
For dynamic content, expose a server route that returns the URLs.
// server/api/__sitemap__/urls.ts
export default defineEventHandler(async () => {
const posts = await queryCollection('blog').all();
return posts.map((post) => ({
loc: `/blog/${post.slug}`,
lastmod: post.updatedAt,
changefreq: 'monthly',
priority: 0.7
}));
});
For very large sites, the module produces a sitemap index pointing to multiple per-section sitemaps automatically.
11.4 The Robots Module Configuration
export default defineNuxtConfig({
robots: {
sitemap: 'https://thatdeveloperguy.com/sitemap.xml',
groups: [
{ userAgent: ['*'], allow: ['/'], disallow: ['/admin', '/api', '/preview'] },
{ userAgent: ['GPTBot', 'ClaudeBot', 'PerplexityBot', 'Google-Extended'], allow: ['/'] }
]
}
});
The explicit allow rules for AI crawlers give explicit signal to AI search platforms that the content is intended to be available. Cross-reference: framework-aicitations.md and framework-aioverviews.md.
11.5 The link-checker Module
Nuxt's link-checker module (bundled in @nuxtjs/seo) scans rendered HTML during development and flags broken internal links, missing alt text, and other quality issues.
export default defineNuxtConfig({
linkChecker: { enabled: true, failOnError: true }
});
Fail-on-error mode is appropriate for CI/CD: a broken link blocks the build.
12. Migration to and from Nuxt
Migration scenarios fall into three categories: upgrading Nuxt 2 to Nuxt 3 or 4, migrating from Vue CLI to Nuxt, and migrating between Nuxt and another framework (Next.js, SvelteKit, Astro).
12.1 Nuxt 2 to Nuxt 3 or 4 Upgrade
Nuxt 2 reached end of life on June 30, 2024. Sites still on Nuxt 2 in 2026 are running unsupported code with security risks. The upgrade is a rewrite, not a migration, because Nuxt 2 was Vue 2 with the Options API and Nuxt 3 plus 4 are Vue 3 with the Composition API.
nuxt2_to_nuxt4_upgrade_steps:
step_1_audit:
- Inventory pages, components, modules, plugins
- Identify Vue 2 specific patterns (filters, .sync modifier, Options API)
- List external dependencies and check Vue 3 compatibility
step_2_setup_new_project:
command: "pnpm dlx nuxi@latest init [project-name]"
step_3_migrate_components:
- Convert Options API to Composition API
- v-bind .sync becomes v-model; filters become methods
- this.$store becomes Pinia; this.$axios becomes useFetch
step_4_migrate_modules:
- "@nuxtjs/axios becomes ofetch + useFetch"
- "@nuxtjs/auth-next becomes nuxt-auth-utils or similar"
- "@nuxtjs/style-resources becomes Vite CSS preprocessor config"
step_5_seo_implementation:
- "head() option becomes useHead and useSeoMeta"
- "asyncData becomes useAsyncData or useFetch"
- Install and configure SEO modules
step_6_redirects:
- Map old URL patterns to new
- Configure redirects in nginx or Nuxt middleware
- Test every redirect end-to-end before launch
step_7_launch:
- Deploy to staging; validate every page
- Validate schema, sitemap, robots, hreflang, redirects
- Update DNS or nginx upstream to swap origin
12.2 Vue CLI Migration to Nuxt
Vue CLI is end of life. Migration to Nuxt converts a pure SPA into a server-rendered or statically-generated application, a substantial SEO improvement. Component code ports directly. Vue Router routes become Nuxt pages. Vuex stores become Pinia stores. Axios calls become useFetch. Vue CLI SPAs default to client-side rendering; after migration to Nuxt with universal SSR or static generation, every page renders to HTML at the first byte.
12.3 Next.js to Nuxt Decision Criteria
The decision to migrate from Next.js to Nuxt is rare. The frameworks are architecturally similar; the migration cost is high; the SEO benefit is zero. Migration is justified when team is stronger in Vue than React, hiring Vue developers is easier, the team prefers Vue's template syntax to JSX, or Vercel costs are unsustainable and self-hosting Nuxt on Bubbles is simpler. Migration is not justified by fashion, marginal expected benefit versus rewrite cost, dependency on React-only libraries, or team split between React and Vue developers.
12.4 SvelteKit to Nuxt
SvelteKit and Nuxt are competing meta-frameworks in similar ecosystems. The migration is a rewrite. The justification is typically team skills (Vue versus Svelte) or ecosystem (Nuxt's module marketplace is larger).
Cross-reference: framework-migration.md for the platform migration strategy including SEO preservation (redirect mapping, sitemap submission, rank tracking through the cutover).
13. Bubbles-Hosted Nuxt
Joseph's Nuxt deployments live on the Bubbles server (Debian, public IP 169.155.162.118, Tailscale 100.90.97.104, nginx fronts everything). The deployment pattern is the same across every Nuxt site: git pull, pnpm install, build, PM2 reload, nginx serves.
13.1 The Deployment Pattern
For SSR sites:
cd /var/www/sites/example.com
git pull origin main
pnpm install --frozen-lockfile
pnpm build
pm2 reload nuxt-example-com --update-env
The pm2 reload performs a zero-downtime restart. Existing connections drain, the new process starts and accepts new connections, the old process exits. For sites pre-rendered via nuxt generate:
cd /var/www/sites/example.com
git pull origin main
pnpm install --frozen-lockfile
pnpm nuxt generate
rsync -av --delete .output/public/ /var/www/sites/example.com-public/
The pre-rendered output sits in a directory that nginx serves. No Node.js process runs in production. The site is a pile of HTML files with zero runtime overhead.
13.2 The PM2 Cluster Configuration
For SSR sites, PM2 runs Nuxt in cluster mode to use multiple CPU cores.
// ecosystem.config.js in /var/www/sites/example.com/
module.exports = {
apps: [{
name: 'nuxt-example-com',
script: './.output/server/index.mjs',
instances: 2,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: '3001',
HOST: '127.0.0.1'
},
error_file: '/var/log/pm2/nuxt-example-com-error.log',
out_file: '/var/log/pm2/nuxt-example-com-out.log',
max_memory_restart: '500M'
}]
};
pm2 start ecosystem.config.js
pm2 save
pm2 startup
Two instances spread requests across two CPU cores. The 500M memory restart limit catches memory leaks before they degrade performance. pm2 startup generates a systemd unit that auto-starts PM2 on boot.
13.3 The Nginx Reverse Proxy Configuration
# /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location /_nuxt/ {
proxy_pass http://127.0.0.1:3001;
add_header Cache-Control "public, immutable, max-age=31536000";
}
location / {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
The 80 to 443 redirect catches HTTP visitors. The www to non-www redirect canonicalizes per the network-wide non-www standard. The Nuxt static assets cache for one year (immutable). Everything else proxies to PM2.
For pre-rendered sites, the nginx config is simpler: the root points to the static output directory and nginx serves HTML directly with no Node.js involved.
server {
listen 443 ssl http2;
server_name example.com;
root /var/www/sites/example.com-public;
index index.html;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location /_nuxt/ {
expires 1y;
add_header Cache-Control "public, immutable, max-age=31536000";
}
location / {
try_files $uri $uri/ $uri.html =404;
}
}
13.4 Letsencrypt and Deploy Hooks
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run
For continuous deployment from a git push, configure a post-receive hook in a bare git repository on Bubbles.
# /home/user/repos/example.com.git/hooks/post-receive
#!/bin/bash
GIT_WORK_TREE=/var/www/sites/example.com git checkout -f main
cd /var/www/sites/example.com
pnpm install --frozen-lockfile
pnpm build
pm2 reload nuxt-example-com --update-env
# On local machine
git remote add bubbles user@bubbles:/home/user/repos/example.com.git
git push bubbles main
The deploy is a git push to a server you control. Joseph's Bubbles network operates entirely without third-party CI/CD or hosting platforms.
13.5 The Bubbles Resource Profile for Nuxt
bubbles_nuxt_resource_profile:
static_generated_site:
memory: "Negligible. Nginx serves static files."
cpu: "Under 1 percent typically."
capacity: "Tens of thousands of concurrent visitors."
use_when: "Marketing sites, blogs, documentation with infrequent updates."
ssr_site_pm2_cluster:
memory: "150 MB idle to 400 MB under load per PM2 instance."
cpu: "Variable; renders pages on demand."
capacity: "Hundreds of concurrent visitors depending on rendering complexity."
use_when: "Personalized content, dynamic data, frequent updates."
hybrid_routerules:
memory: "150 MB per PM2 instance."
cpu: "Lower than full SSR because pre-rendered routes bypass Node."
use_when: "Mix of marketing pages and dynamic features."
Joseph's Bubbles server has 16 GB of RAM. Three to five Nuxt SSR sites can run comfortably. Twenty pre-rendered Nuxt sites can run trivially.
14. Nuxt SEO Audit Checklist
The 20-criterion Nuxt-specific audit. Run on every Nuxt project before launch and quarterly thereafter.
nuxt_seo_audit_checklist:
1_useHead_coverage:
check: "Every page calls useHead or useSeoMeta with title, description, canonical."
validate: "View source; confirm unique title, description, canonical per page."
2_title_template:
check: "nuxt.config.ts app.head.titleTemplate configured with brand suffix."
validate: "Every page title ends with ' | Brand'."
3_metadata_base_url:
check: "Runtime config publicSiteUrl or site.url configured."
validate: "useSeoMeta resolves ogUrl to absolute URLs."
4_canonical_strategy:
check: "Canonical handled via composable or layout, applied every page."
validate: "<link rel='canonical'> matches current URL."
5_open_graph_complete:
check: "useSeoMeta sets ogTitle, ogDescription, ogImage, ogUrl, ogType."
validate: "Five og:* meta tags present per page."
6_twitter_card_complete:
check: "useSeoMeta sets twitterCard, twitterTitle, twitterDescription, twitterImage."
7_schema_org_module_installed:
check: "nuxt-schema-org installed; useSchemaOrg called in app.vue with Organization, WebSite, WebPage."
validate: "JSON-LD @graph array present."
8_per_page_schema:
check: "Pages with content type call useSchemaOrg with matching type (Article, Product, LocalBusiness)."
validate: "Google Rich Results Test passes on each page type."
9_sitemap_module_installed:
check: "@nuxtjs/sitemap installed; /sitemap.xml returns valid XML with all routes."
10_robots_module_installed:
check: "@nuxtjs/robots installed; /robots.txt has sitemap line and AI crawler allow rules."
11_og_image_generation:
check: "nuxt-og-image installed; per-page og:images generated."
validate: "/__og-image__/og.png renders dynamically."
12_image_module_in_use:
check: "@nuxt/image installed; NuxtImg used throughout."
validate: "Img tags include srcset and modern formats."
13_i18n_module_if_multilingual:
check: "Multilingual sites use @nuxtjs/i18n with seo: true."
validate: "Hreflang link tags present per locale."
14_lighthouse_performance:
check: "Lighthouse Performance at or above 90 on key landing pages."
validate: "Run Lighthouse on home, services, primary blog post."
15_lcp_under_2_5s:
check: "Largest Contentful Paint under 2.5 seconds p75 field data."
validate: "CrUX or Search Console Core Web Vitals."
16_inp_under_200ms:
check: "Interaction to Next Paint under 200ms p75 field data."
17_cls_under_0_1:
check: "Cumulative Layout Shift under 0.1 p75."
18_404_returns_404:
check: "Missing dynamic routes return HTTP 404 not 200."
validate: "Curl non-existent route; confirm 404 status."
19_redirects_return_301:
check: "Permanent redirects use 301 not 302."
20_no_render_blocking_resources:
check: "No render-blocking JavaScript or CSS in head."
validate: "Lighthouse audit; Chrome DevTools Coverage tab."
Any failed criterion becomes a backlog item.
End of Framework
This framework specifies Nuxt SEO implementation for Joseph's Bubbles-hosted Nuxt sites in 2026. The patterns apply to Nuxt 3 with the appropriate version qualifications and Nuxt 4 as the default in 2026.
Cross-references:
- framework-cross-stack-implementation.md for Nuxt patterns versus other stacks
- framework-nextjs.md for the Next.js equivalent of every pattern
- framework-sveltekit.md for the SvelteKit equivalent
- framework-schema.md for the complete Schema.org specification
- framework-hreflang.md for the deep hreflang specification
- framework-international.md for international SEO strategy
- framework-migration.md for the platform migration playbook
- framework-pageexperience.md for Core Web Vitals
- framework-headless.md for headless CMS integration with Nuxt
- framework-technicalseo.md for cross-stack technical SEO
- framework-mobileseo.md for mobile-specific SEO
- framework-aicitations.md for AI search crawler patterns
- framework-aioverviews.md for Google AI Overviews
- framework-imageseo.md for image SEO
Want this framework implemented on your site?
ThatDevPro ships these frameworks as productized services. SDVOSB-certified veteran owned. Cassville, Missouri.
See Engine Optimization service ›