DEV Community

A0mineTV
A0mineTV

Posted on

Complete SEO Meta Tags Implementation with Nuxt 3: From Basic Meta to Dynamic OG Images

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

⚙️ 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 }
  }
})
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

🔍 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">
})
Enter fullscreen mode Exit fullscreen mode

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 }]
})
Enter fullscreen mode Exit fullscreen mode

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
    })
  }]
})
Enter fullscreen mode Exit fullscreen mode

🖼️ 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
})

Enter fullscreen mode Exit fullscreen mode

How Dynamic OG Images Work

  1. URL Generation: The page creates an OG image URL with the title as a parameter
  2. API Route: /api/og receives the title and generates a custom image
  3. Satori Processing: Converts HTML/CSS to SVG
  4. Image Conversion: Resvg converts SVG to PNG
  5. 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
Enter fullscreen mode Exit fullscreen mode

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}`
})
Enter fullscreen mode Exit fullscreen mode

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) }]
})
Enter fullscreen mode Exit fullscreen mode

🚀 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:

  1. Search Engine Optimization: Proper meta tags and structured data
  2. Social Media Sharing: Rich previews on Twitter, Facebook, LinkedIn
  3. Accessibility: Semantic HTML and proper heading structure
  4. Performance: Server-side generation and caching
  5. 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:

  1. Facebook Sharing Debugger: Test OG tags
  2. Twitter Card Validator: Verify Twitter cards
  3. Google Rich Results Test: Check structured data
  4. 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.

Top comments (0)