Skip to content

Headless & Inertia.js Integration

RankForge is designed with first-class support for headless Laravel architectures, single-page applications (SPAs), and Inertia.js (Vue 3 / React).


Inertia.js (Vue 3)

1. Share SEO Data in Middleware

In your HandleInertiaRequests middleware:

php
namespace App\Http\Middleware;

use Eamirgh\RankForge\Facades\RankForge;
use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'seo' => fn () => RankForge::toArray(),
        ]);
    }
}

2. Render with Inertia <Head> in Vue 3

In your root layout or page component (e.g., resources/js/Layouts/AppLayout.vue):

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

const page = usePage();
const seo = computed(() => page.props.seo);
</script>

<template>
  <div>
    <Head>
      <!-- Title & Basic Meta -->
      <title>{{ seo.title }}</title>
      <meta name="description" :content="seo.description" />
      <meta name="robots" :content="seo.robots" />
      <link v-if="seo.canonical" rel="canonical" :href="seo.canonical" />

      <!-- Keywords -->
      <meta v-if="seo.keywords?.length" name="keywords" :content="seo.keywords.join(', ')" />

      <!-- Open Graph -->
      <template v-if="seo.open_graph">
        <meta v-for="(val, key) in seo.open_graph" :key="key" :property="`og:${key}`" :content="val" />
      </template>

      <!-- Twitter Card -->
      <template v-if="seo.twitter">
        <meta v-for="(val, key) in seo.twitter" :key="key" :name="`twitter:${key}`" :content="val" />
      </template>

      <!-- Hreflang Alternates -->
      <template v-if="seo.hreflang">
        <link v-for="(href, lang) in seo.hreflang" :key="lang" rel="alternate" :hreflang="lang" :href="href" />
      </template>

      <!-- JSON-LD Structured Data -->
      <component
        :is="'script'"
        v-for="(schema, idx) in seo.json_ld"
        :key="idx"
        type="application/ld+json"
        v-html="JSON.stringify(schema)"
      />
    </Head>

    <slot />
  </div>
</template>

Inertia.js (React)

In your root layout or page component (e.g., resources/js/Layouts/AppLayout.tsx):

tsx
import { Head, usePage } from '@inertiajs/react';
import React from 'react';

export default function AppLayout({ children }: { children: React.ReactNode }) {
  const { seo } = usePage<{ seo: any }>().props;

  return (
    <>
      <Head>
        <title>{seo.title}</title>
        <meta name="description" content={seo.description} />
        <meta name="robots" content={seo.robots} />
        {seo.canonical && <link rel="canonical" href={seo.canonical} />}

        {/* Open Graph */}
        {seo.open_graph && Object.entries(seo.open_graph).map(([key, val]) => (
          <meta key={key} property={`og:${key}`} content={String(val)} />
        ))}

        {/* Twitter */}
        {seo.twitter && Object.entries(seo.twitter).map(([key, val]) => (
          <meta key={key} name={`twitter:${key}`} content={String(val)} />
        ))}

        {/* JSON-LD */}
        {seo.json_ld?.map((schema: any, idx: number) => (
          <script
            key={idx}
            type="application/ld+json"
            dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
          />
        ))}
      </Head>

      <main>{children}</main>
    </>
  );
}

Headless JSON API Serialization

If you are using Laravel as a pure API backend for Next.js, Nuxt, or Astro:

php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Post;
use Eamirgh\RankForge\Facades\RankForge;
use Illuminate\Http\JsonResponse;

class PostController extends Controller
{
    public function show(Post $post): JsonResponse
    {
        RankForge::forModel($post);

        return response()->json([
            'data' => $post,
            'seo' => RankForge::toArray(),
        ]);
    }
}

Released under the MIT License.