PHP

SEO Metadata in Laravel Inertia & Vue — Full Guide With Examples

ME
Mohamed Elogail
3 days ago
5 min read
SEO Metadata in Laravel Inertia & Vue — Full Guide With Examples

Search Engine Optimization (SEO) plays a major role in helping search engines understand the content of your pages. For a blog, SEO metadata is even more important because each post needs its own unique title, description, image, and structured data.

When using Laravel + Inertia.js + Vue, you don't have a traditional Blade <head> section for every page load. Instead, you generate SEO metadata in Laravel and pass it to your Vue layout, which updates the <head> dynamically using Inertia's <Head> component.

In this article, you'll learn how to implement a complete SEO system, including which metadata to add, how to generate it for each blog post, and how to render it properly in Vue.

What will you learn?

  • What tags to include in a blog post.
  • How to structure them.
  • How to generate them in Laravel.
  • How to pass them through Inertia.
  • How to render them in Vue.
  • How the SEO helper fits into the entire workflow.

In this article, I assume that you have a basic understanding of SEO metadata and how you can implement them inside the <head> tag in ordinary HTML or a Laravel Blade file.

What SEO Metadata Should a Blog Post Include?

A typical blog post should include:

1- Standard SEO Tags

These help Google understand your content:

  • <title>
  • <meta name="description">
  • <meta name="keywords"> (optional but acceptable).
  • <meta name="author">
  • <link rel="canonical"> (to prevent duplicate indexing)

 

<title>How to Use Laravel Queues — A Complete Guide</title>
<meta name="description" content="Learn how Laravel queues work with jobs, workers, and real-world examples.">
<meta name="keywords" content="Laravel, Queues, Jobs, PHP, Inertia">
<meta name="author" content="John Doe" />
<link rel="canonical" href="https://example.com/blog/how-to-use-laravel-queues">

2- Open Graph Tags (Facebook, LinkedIn, Instagram, etc.)

Used for social sharing:

  • og:title
  • og:derscription
  • og:image
  • og:url
  • og:type (article for blog posts)
<meta property="og:title" content="How to Use Laravel Queues — A Complete Guide" />
<meta property="og:description" content="Learn how Laravel queues work with real examples." />
<meta property="og:image" content="https://example.com/storage/posts/123-cover.jpg" />
<meta property="og:url" content="https://example.com/blog/how-to-use-laravel-queues" />
<meta property="og:type" content="article" />

3- Twitter Card Tags

For Twitter/X previews:

  • twitter:title
  • twitter:descrion
  • twitter:image
  • twitter:card (usually "summary_large_image")
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="How to Use Laravel Queues — A Complete Guide" />
<meta name="twitter:description" content="Learn how Laravel queues work with real examples." />
<meta name="twitter:image" content="https://example.com/storage/posts/123-cover.jpg" />

4- JSON-LD Structured Data

Google recommends HTML <script type="application/ld+json"> to help them understand your article:

  • "@type": "BlogPosting"
  • "headline"
  • "image"
  • "datePublished"
  • "author"
  • "description"
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "How to Use Laravel Queues — A Complete Guide",
  "image": "https://example.com/storage/posts/123-cover.jpg",
  "author": {
    "@type": "Person",
    "name": "John Doe"
  },
  "datePublished": "2024-11-10",
  "description": "Learn how Laravel queues work with real examples."
}

5- Optional Additional Tags

Useful but not required:

  • <meta name="robots">
  • <meta name="theme-color">

We will generate all of these automatically using a custom Laravel SEO helper.

Laravel Side - Building a Reusable SEO Helper

To avoid repeating code in every controller, we will create a global SEO helper that builds metadata arrays for different models.

1- Create app/Helpers/Seo.php file

The file contains two static methods for easy use without instantiating - make() and forPost().

<?php

namespace App\Helpers;

class Seo
{
    public static function make(array $data): array
    {
        return [
            'title'        => $data['title']        ?? config('app.name'),
            'description'  => $data['description']  ?? '',
            'keywords'     => $data['keywords']     ?? '',
            'author'       => $data['author']       ?? '',
            'image'        => $data['image']        ?? asset('default-cover.png'),
            'url'          => $data['url']          ?? url()->current(),
            'type'         => $data['type']         ?? 'article',
            'published_at' => $data['published_at'] ?? null,
            'json_ld'      => $data['json_ld']      ?? [],
        ];
    }

    public static function forPost($post): array
    {
        return self::make([
            'title'       => $post->seo_title ?? $post->title,
            'description' => $post->seo_description ?? substr(strip_tags($post->excerpt), 0, 160),
            'keywords'    => $post->seo_keywords ?? '',
            'image'       => $post->cover_image_url,
            'url'         => route('posts.show', $post->slug),
            'author'      => $post->author->name,
            'published_at' => $post->published_at?->toDateString(),
            'json_ld' => [
                "@context"       => "https://schema.org",
                "@type"          => "BlogPosting",
                "headline"       => $post->title,
                "image"          => $post->cover_image_url,
                "url"            => route('posts.show', $post->slug),
                "author"         => [
                    "@type" => "Person",
                    "name"  => $post->author->name
                ],
                "datePublished" => $post->published_at?->toDateString(),
                "description"   => $post->seo_description ?? substr(strip_tags($post->excerpt), 0, 160)
            ],
        ]);
    }
}

This helper returns a clean SEO array with everything your Vue layout needs.

2- Autoload the Helper

Add this to composer.json so Laravel loads the helper automatically:

"autoload": {
    "files": [
        "app/Helpers/Seo.php"
    ]
}

Then run in your terminal:

composer dump-autoload -o

Now you can call the helper anywhere in your application.

3- Passing SEO Metadata to Inertia

In your PostController, include the SEO data when returning the post page.

use App\Helpers\Seo;

public function show(Post $post)
{
    return inertia('Blog/Show', [
        'post' => $post,
        'seo'  => Seo::forPost($post),
    ]);
}

Now Show.vue and your layout will receive:

page.props.seo

Vue Side - Display Metadata in <Head>

Your website probably uses a main layout such as:

resources/js/Layouts/MainLayout.vue

Inside the layout, render SEO tags dynamically:

<script setup>
import { usePage, Head } from '@inertiajs/vue3'

const page = usePage()
const seo = page.props.seo ?? {}
</script>

<template>
  <Head>
    <!-- Basic SEO -->
    <title>{{ seo.title }}</title>
    <meta name="description" :content="seo.description" />
    <meta name="keywords" :content="seo.keywords" />
    <meta name="author" :content="seo.author" />
    <link rel="canonical" :href="seo.url" />

    <!-- Open Graph -->
    <meta property="og:title" :content="seo.title" />
    <meta property="og:description" :content="seo.description" />
    <meta property="og:image" :content="seo.image" />
    <meta property="og:url" :content="seo.url" />
    <meta property="og:type" :content="seo.type" />

    <!-- Twitter -->
    <meta name="twitter:title" :content="seo.title" />
    <meta name="twitter:description" :content="seo.description" />
    <meta name="twitter:image" :content="seo.image" />
    <meta name="twitter:card" content="summary_large_image" />

    <!-- JSON-LD -->
    <component :is="'script'" type="application/ld+json" v-html="JSON.stringify(seo.json_ld)"></component>

  </Head>

  <slot />
</template>

We used a dynamic component with JSON-LD because we can't use <script> tags directly inside Vue <tamplate>, and if you are using Vite with Laravel, it will fire a runtime error.

So, the dynamic component <component :is="'script'" >:

  • Renders a native <script> tag at runtime.
  • It bypasses the Vite compiler's static check because it's not a literal <script> tag in the source code.
  • v-html ensures JSON content is injected without being escaped (preserving quotes and structure).

Why Is This Approach The Best?

This method has several advantages:

  • Centralized SEO logic: One helper for everything.
  • Clean controller: No duplicated meta tag building.
  • Dynamic Vue <head> rendering: Works perfectly with inertia SPA-style navigation.
  • Google-ready structured data: JSON-LD improves search ranking.
  • Social media preview support: Open Graph + Twitter Cards included.
  • Easy to extend: You can add og:site_name, robots, or custom fields anytime.

Conclusion

Integrating SEO metadata in a Laravel + Inertia.js + Vue application doesn't have to be complicated. By generating your metadata on the backend using a simple helper and rendering it dynamically in your Vue layout, you get a clean, scalable, and fully optimized system for managing blog post SEO.

This setup ensures your post looks great on Google, Social Media, and anywhere else they appear - all while keeping your code elegant and maintainable.

Mogail logo

Crafting digital experiences that inspire and engage. Let's build something amazing together.

Cairo, Egypt

Quick Links

Stay Updated

Subscribe to our newsletter for the latest updates, news, and exclusive content delivered straight to your inbox.

We respect your privacy. Unsubscribe anytime.

10,000+ subscribers

© 2025 Mogail. All rights reserved.