Technical SEO cho Laravel Blog: JSON-LD và Open Graph

· 6 min read

Trong hệ sinh thái blog Markdown file-based, nội dung là vua, nhưng metadata là vương miện. Không có structured data và meta tag phù hợp, ngay cả nội dung tốt nhất cũng có thể bị lạc trong dòng chảy của search engine.

Hướng dẫn này tập trung vào việc triển khai Technical SEO cho Laravel blog, đảm bảo nội dung của bạn trông đẹp trên kết quả tìm kiếm Google và chia sẻ trên mạng xã hội.

Chiến Lược

Chúng ta sẽ tập trung vào ba lĩnh vực chính:

  1. Meta Tag Tiêu Chuẩn: Title, description, canonical URL.
  2. Open Graph (OG) & Twitter Card: Cho preview mạng xã hội.
  3. JSON-LD Structured Data: Giúp search engine hiểu nội dung của bạn (Article, Breadcrumb).

1. Head Component

Thay vì làm rối layout.blade.php, tạo một component SEO chuyên dụng.

php artisan make:component SeoMetadata

Trong resources/views/components/seo-metadata.blade.php:

@props(['post' => null, 'title' => 'Blog Kỹ Thuật', 'description' => 'Blog về Laravel và code.'])

@php
    $pageTitle = $post ? $post->title . ' | Blog Kỹ Thuật' : $title;
    $pageDescription = $post ? $post->description : $description;
    $currentUrl = url()->current();
    $ogImage = $post && $post->cover ? asset('images/posts/' . $post->slug . '/' . $post->cover) : asset('images/default-og.jpg');
@endphp

<title>{{ $pageTitle }}</title>
<meta name="description" content="{{ $pageDescription }}">
<link rel="canonical" href="{{ $currentUrl }}">

<!-- Open Graph / Facebook -->
<meta property="og:type" content="{{ $post ? 'article' : 'website' }}">
<meta property="og:url" content="{{ $currentUrl }}">
<meta property="og:title" content="{{ $pageTitle }}">
<meta property="og:description" content="{{ $pageDescription }}">
<meta property="og:image" content="{{ $ogImage }}">

<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:url" content="{{ $currentUrl }}">
<meta name="twitter:title" content="{{ $pageTitle }}">
<meta name="twitter:description" content="{{ $pageDescription }}">
<meta name="twitter:image" content="{{ $ogImage }}">

Cách sử dụng trong layout:

<head>
    <x-seo-metadata :post="$post ?? null" />
    <!-- ... css ... -->
</head>

2. Triển Khai JSON-LD

JSON-LD (JavaScript Object Notation for Linked Data) là định dạng Schema.org ưa thích của Google. Nó cho phép chúng ta nói rõ ràng với Google: "Đây là một Blog Posting, được viết bởi X, xuất bản vào ngày Y."

Chúng ta có thể thêm trực tiếp vào Blade view cho bài viết đơn lẻ.

@if($post)
    <script type="application/ld+json">
    {
        "@context": "https://schema.org",
        "@type": "BlogPosting",
        "headline": "{{ $post->title }}",
        "image": [
            "{{ asset('images/posts/' . $post->slug . '/' . $post->cover) }}"
        ],
        "datePublished": "{{ $post->date->toIso8601String() }}",
        "dateModified": "{{ $post->updated_at ? $post->updated_at->toIso8601String() : $post->date->toIso8601String() }}",
        "author": [{
            "@type": "Person",
            "name": "Tên Của Bạn",
            "url": "{{ url('/') }}"
        }],
        "publisher": {
          "@type": "Organization",
          "name": "Blog Kỹ Thuật",
          "logo": {
            "@type": "ImageObject",
            "url": "{{ asset('images/logo.png') }}"
          }
        },
        "description": "{{ $post->description }}"
    }
    </script>
@endif

Tại Sao Render Động?

Vì nội dung của chúng ta là Markdown tĩnh, chúng ta cần truyền các giá trị này vào view một cách động. Sử dụng Carbon cho ngày tháng đảm bảo định dạng ISO 8601 chính xác mà Google yêu cầu.

3. Sitemap Động

sitemap.xml rất quan trọng để cho phép crawler khám phá URL của bạn. Vì chúng ta sử dụng hệ thống file-based, chúng ta có thể tạo nó on the fly hoặc cache nó.

Định nghĩa route:

Route::get('/sitemap.xml', function () {
    $posts = \App\Facades\Markdown::getAllPosts();
    
    return response()->view('sitemap', [
        'posts' => $posts
    ])->header('Content-Type', 'text/xml');
});

View resources/views/sitemap.blade.php:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc>{{ url('/') }}</loc>
        <lastmod>{{ now()->toAtomString() }}</lastmod>
        <changefreq>daily</changefreq>
        <priority>1.0</priority>
    </url>
    @foreach ($posts as $post)
    <url>
        <loc>{{ route('blog.show', $post->slug) }}</loc>
        <lastmod>{{ $post->date->toAtomString() }}</lastmod>
        <changefreq>monthly</changefreq>
        <priority>0.8</priority>
    </url>
    @endforeach
</urlset>

Kiểm Tra Implementation

Đừng đoán—hãy xác minh.

  1. Rich Results Test: Sử dụng công cụ Rich Results Test của Google. Dán HTML được tạo hoặc URL của bạn để xác minh JSON-LD hợp lệ.
  2. Meta Tag Debugger: Sử dụng Metatags.io để preview cách Twitter card và ảnh Open Graph của bạn trông như thế nào.

Tóm Tắt

Bằng cách trừu tượng hóa meta tag vào Blade component và inject JSON-LD động, chúng ta đảm bảo mọi file Markdown chúng ta tạo tự động nhận được SEO top-tier mà không cần công sức thủ công thêm. Đây là vẻ đẹp của custom Laravel blog engine so với static site generator—bạn có toàn quyền kiểm soát rendering pipeline.

Bình luận