This content originally appeared on DEV Community and was authored by A0mineTV
SEO is crucial for web applications, but implementing it correctly can be challenging. In this article, we’ll explore how to create a comprehensive SEO solution using Nuxt 3, including meta tags, Open Graph images, JSON-LD structured data, and dynamic OG image generation.
What We’ll Build
We’ll create a complete SEO implementation featuring:
- Meta tags for search engines
- Open Graph and Twitter Card tags for social sharing
- JSON-LD structured data for rich snippets
- Dynamic OG image generation using Satori
- Canonical URLs for duplicate content prevention
Project Setup
First, let’s set up our Nuxt 3 project with the necessary dependencies:
{
"dependencies": {
"@prisma/client": "^6.16.2",
"@resvg/resvg-js": "^2.6.2",
"nuxt": "^4.1.2",
"satori": "^0.18.3",
"satori-html": "^0.3.2",
"vue": "^3.5.21"
},
"devDependencies": {
"@nuxt/image": "^1.11.0"
}
}
Nuxt Configuration
Configure your runtime settings in nuxt.config.ts
:
export default defineNuxtConfig({
devtools: { enabled: true },
pages: true,
modules: ['@nuxt/image'],
runtimeConfig: {
public: {
siteUrl: process.env.NUXT_PUBLIC_SITE_URL || 'http://localhost:3001'
}
},
experimental: {
payloadExtraction: false
},
nitro: {
preset: 'node-server',
experimental: { wasm: true }
}
})
The siteUrl
configuration is essential for generating absolute URLs for canonical links and Open Graph images.
SEO Meta Implementation
Here’s our complete SEO demo page (app/pages/seo-demo.vue
):
<script setup lang="ts">
const route = useRoute()
const siteUrl = useRuntimeConfig().public.siteUrl
const canonical = new URL(route.fullPath || '/seo-demo', siteUrl).toString()
const title = "Nuxt SEO Demo"
const description = "Example page showing proper meta tags and JSON-LD."
const ogImage = `${siteUrl}/api/og?title=${encodeURIComponent(title)}`
useSeoMeta({
title,
description,
ogTitle: title,
ogDescription: description,
ogUrl: canonical,
ogType: 'website',
ogImage,
twitterCard: 'summary_large_image',
twitterTitle: title,
twitterDescription: description,
twitterImage: ogImage
})
useHead({
link: [{ rel: 'canonical', href: canonical }],
script: [{
type: 'application/ld+json',
children: JSON.stringify({
"@context": "https://schema.org",
"@type": "WebPage",
"name": title,
"description": description,
"url": canonical
})
}]
})
</script>
<template>
<main class="wrap">
<h1>{{ title }}</h1>
<p>This page sets SEO meta, OG/Twitter, and JSON-LD.</p>
<p>Canonical: <code>{{ canonical }}</code></p>
</main>
</template>
Breaking Down the SEO Implementation
1. Meta Tags with useSeoMeta()
The useSeoMeta()
composable is Nuxt 3’s recommended way to handle meta tags:
useSeoMeta({
title, // <title> tag
description, // <meta name="description">
ogTitle: title, // <meta property="og:title">
ogDescription: description, // <meta property="og:description">
ogUrl: canonical, // <meta property="og:url">
ogType: 'website', // <meta property="og:type">
ogImage, // <meta property="og:image">
twitterCard: 'summary_large_image', // <meta name="twitter:card">
twitterTitle: title, // <meta name="twitter:title">
twitterDescription: description, // <meta name="twitter:description">
twitterImage: ogImage // <meta name="twitter:image">
})
2. Canonical URLs
Canonical URLs prevent duplicate content issues:
const canonical = new URL(route.fullPath || '/seo-demo', siteUrl).toString()
useHead({
link: [{ rel: 'canonical', href: canonical }]
})
3. JSON-LD Structured Data
Structured data helps search engines understand your content:
useHead({
script: [{
type: 'application/ld+json',
children: JSON.stringify({
"@context": "https://schema.org",
"@type": "WebPage",
"name": title,
"description": description,
"url": canonical
})
}]
})
Dynamic OG Image Generation
The magic happens in our API route (server/api/og.get.ts
):
import satori from 'satori'
import { Resvg } from '@resvg/resvg-js'
import { readFile } from 'node:fs/promises'
import { html } from 'satori-html'
export default defineEventHandler(async (event) => {
const q = getQuery(event)
const title = String(q.title ?? 'Nuxt SEO Demo')
// Load font from public/fonts directory
const font = await readFile('public/fonts/Inter-Regular.otf')
// Create HTML template using satori-html
const markup = html`<div style="
width:1200px;height:630px;display:flex;align-items:center;justify-content:center;
font-size:56px;font-family:Inter;padding:40px;background:#0ea5e9;color:#fff">
${title}
</div>`
// Generate SVG using Satori
const svg = await satori(markup, {
width: 1200,
height: 630,
fonts: [{ name: 'Inter', data: font, weight: 400, style: 'normal' }]
})
// Convert SVG to PNG using Resvg
const png = new Resvg(svg).render().asPng()
setHeader(event, 'content-type', 'image/png')
return png
})
How Dynamic OG Images Work
- URL Generation: The page creates an OG image URL with the title as a parameter
-
API Route:
/api/og
receives the title and generates a custom image - Satori Processing: Converts HTML/CSS to SVG
- Image Conversion: Resvg converts SVG to PNG
- Caching: Nuxt automatically caches the generated images
Font Setup
For custom fonts in OG images, place your font file in public/fonts/
:
public/
fonts/
Inter-Regular.otf
The font is loaded server-side and used by Satori for text rendering.
Advanced SEO Patterns
Dynamic Meta Based on Content
// For blog posts or dynamic content
const { data: post } = await $fetch(`/api/posts/${route.params.slug}`)
useSeoMeta({
title: post.title,
description: post.excerpt,
ogImage: `/api/og?title=${encodeURIComponent(post.title)}&author=${post.author}`
})
Multiple JSON-LD Types
// Article with author information
const jsonLd = {
"@context": "https://schema.org",
"@type": "Article",
"headline": title,
"description": description,
"author": {
"@type": "Person",
"name": "Your Name"
},
"datePublished": "2024-01-01",
"url": canonical
}
useHead({
script: [{ type: 'application/ld+json', children: JSON.stringify(jsonLd) }]
})
Performance Considerations
Image Optimization
- Caching: Generated OG images are automatically cached by Nuxt
- Size: Standard 1200x630px for optimal social sharing
- Format: PNG for best compatibility across platforms
Server-Side Generation
- OG images are generated server-side, reducing client load
- Font loading happens once per build
- Satori provides excellent performance for SVG generation
SEO Benefits
This implementation provides:
- Search Engine Optimization: Proper meta tags and structured data
- Social Media Sharing: Rich previews on Twitter, Facebook, LinkedIn
- Accessibility: Semantic HTML and proper heading structure
- Performance: Server-side generation and caching
- Flexibility: Dynamic content-based meta generation
Best Practices
Meta Tag Guidelines
- Title: Keep under 60 characters
- Description: 150-160 characters for optimal display
- OG Image: Always use absolute URLs
- Canonical: Prevent duplicate content penalties
Technical SEO
- Use semantic HTML structure
- Implement proper heading hierarchy (h1, h2, h3)
- Include alt attributes for images
- Ensure fast loading times
Testing Your SEO
Tools to validate your implementation:
- Facebook Sharing Debugger: Test OG tags
- Twitter Card Validator: Verify Twitter cards
- Google Rich Results Test: Check structured data
- Chrome DevTools: Inspect generated meta tags
Conclusion
This comprehensive SEO implementation demonstrates how Nuxt 3 makes it easy to create SEO-friendly applications. The combination of:
- useSeoMeta() for meta tag management
- Dynamic OG image generation with Satori
- JSON-LD structured data for rich snippets
- Canonical URLs for duplicate content prevention
Provides a solid foundation for any web application that needs excellent search engine visibility and social media sharing capabilities.
The server-side generation ensures fast performance while the dynamic nature allows for content-specific optimization. This approach scales well from simple marketing sites to complex web applications.
This content originally appeared on DEV Community and was authored by A0mineTV