Tạo RSS Feed và Sitemap trong Laravel

· 5 min read

RSS chưa chết. Trong cộng đồng developer, RSS vẫn là tiêu chuẩn vàng để tiêu thụ nội dung. Nếu blog của bạn không có RSS feed (/feed), nhiều developer (bao gồm cả tôi) sẽ không subscribe.

Tương tự, Sitemap (/sitemap.xml) là bản đồ bạn đưa cho crawler của Google.

Đây là cách triển khai cả hai theo cách Laravel sạch sẽ, tối giản.

1. RSS Feed

Chúng ta muốn tạo một XML response tuân theo đặc tả RSS 2.0 hoặc Atom.

Route

// routes/web.php
Route::get('/feed', [FeedController::class, 'index'])->name('feed');

Controller

namespace App\Http\Controllers;

use App\Facades\Markdown;
use Illuminate\Http\Response;

class FeedController extends Controller
{
    public function index()
    {
        $posts = Markdown::getAllPosts()
            ->reject(fn($post) => $post->draft)
            ->take(20);

        $content = view('feeds.rss', [
            'posts' => $posts,
            'meta' => [
                'title' => 'Blog Kỹ Thuật Của Tôi',
                'description' => 'Code, Laravel, và Suy Nghĩ.',
                'link' => url('/'),
                'lang' => 'vi-vn',
            ]
        ])->render();

        return response($content, 200, [
            'Content-Type' => 'application/xml; charset=UTF-8',
        ]);
    }
}

View (resources/views/feeds/rss.blade.php)

<?= '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>{{ $meta['title'] }}</title>
        <link>{{ $meta['link'] }}</link>
        <description>{{ $meta['description'] }}</description>
        <language>{{ $meta['lang'] }}</language>
        <atom:link href="{{ url('/feed') }}" rel="self" type="application/rss+xml" />
        
        @foreach($posts as $post)
        <item>
            <title><![CDATA[{{ $post->title }}]]></title>
            <link>{{ route('blog.show', $post->slug) }}</link>
            <guid isPermaLink="true">{{ route('blog.show', $post->slug) }}</guid>
            <description><![CDATA[{{ $post->description }}]]></description>
            <pubDate>{{ $post->date->toRssString() }}</pubDate>
            <author>me@example.com (Tên Của Bạn)</author>
        </item>
        @endforeach
    </channel>
</rss>

Pro Tip: Instance Carbon của Laravel trên $post->date có method toRssString() tiện lợi dành riêng cho việc này.

2. Sitemap

Sitemap đơn giản hơn. Chúng chỉ liệt kê URL và ngày sửa đổi.

Controller Method

public function sitemap()
{
    $posts = Markdown::getAllPosts()->reject(fn($p) => $p->draft);
    
    return response()->view('feeds.sitemap', [
        'posts' => $posts
    ])->header('Content-Type', 'text/xml');
}

View (resources/views/feeds/sitemap.blade.php)

<?= '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    {{-- Trang Tĩnh --}}
    <url>
        <loc>{{ url('/') }}</loc>
        <changefreq>daily</changefreq>
        <priority>1.0</priority>
    </url>
    <url>
        <loc>{{ url('/about') }}</loc>
        <changefreq>monthly</changefreq>
        <priority>0.5</priority>
    </url>

    {{-- Bài Blog --}}
    @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>

Package vs Tùy Chỉnh

Có một package tuyệt vời spatie/laravel-feed xử lý các cấu hình này.

Tại sao tự làm?

  1. Dependencies: Với blog cá nhân đơn giản, kéo một package chỉ để lặp qua array và in XML có thể là overkill.
  2. Kiểm soát: Bạn có kiểm soát chi tiết về định dạng output chính xác.
  3. Học hỏi: Tốt để hiểu cấu trúc XML bên dưới.

Tuy nhiên, nếu bạn có nhiều feed stream hoặc yêu cầu phức tạp, package của Spatie là lựa chọn phù hợp.

Hoàn Thiện

Thêm link tag vào <head> của layout chính để trình duyệt và RSS reader có thể tự động phát hiện feed:

<link rel="alternate" type="application/rss+xml" title="RSS Feed Blog Kỹ Thuật" href="{{ route('feed') }}" />

Giờ nội dung của bạn mở ra với thế giới, có thể được tiêu thụ bởi bot và con người.

Bình luận