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)