Przejdź do treści

Generowane obrazy OG ​

Od wersji rdzenia 3.20 renderowanie przez Chrome wyłącza JavaScript i blokuje żądania zasobów HTTP(S), FTP i WebSocket. Własne szablony muszą używać statycznego HTML/CSS i osadzonych zasobów, tak jak szablony dołączone do pakietu.

Strona bez własnej karty społecznościowej korzysta z jednego wspólnego default_og_image — tego samego obrazu przy każdym udostępnieniu. Ta funkcja daje każdej stronie własną kartę Open Graph / Twitter 1200×630, renderowaną z szablonu Blade przez rzeczywistą przeglądarkę headless (przez spatie/browsershot). Dzięki temu tytuł zawija się w wierszach, znaki diakrytyczne są renderowane, CJK korzysta z właściwego fontu zastępczego, a zbyt długie tytuły są poprawnie skracane — samodzielnie napisana biblioteka obrazów nie rozwiązuje sama tych problemów.

To bezpłatna funkcja rdzenia, domyślnie wyłączona. Gdy jest wyłączona, default_og_image jest używane bez zmian, a pakiet pozostaje bez dodatkowych zależności.

Statyczne generowanie z wyprzedzeniem — świadome założenie

Karty są generowane z wyprzedzeniem poleceniem Artisan, a nie na bieżąco podczas żądania WWW. Strona odsyła tylko do karty, która już istnieje na dysku — żądanie odwiedzającego nigdy nie uruchamia przeglądarki i nigdy nie odsyła do brakującego obrazu (404). Nie ma endpointu renderowania na żywo (zobacz Ograniczenia).

Wymagania ​

Sterownik przeglądarki jest opcjonalną zależnością, więc bezpłatny rdzeń instaluje się bez niego. Aby włączyć funkcję, potrzebujesz w aplikacji:

bash
composer require spatie/browsershot

Oraz środowiska uruchamianego przez Browsershot:

  • Node.js na hoście.
  • Puppeteer, zainstalowanego w katalogu głównym aplikacji, aby Node go odnalazł:
    bash
    npm install puppeteer
  • Chrome / Chromium — Puppeteer domyślnie pobiera własny Chromium; w produkcji zwykle wskazujesz systemowy Chrome (zobacz chrome_path).

Na Windows zainstaluj puppeteer w katalogu głównym aplikacji

Na Windows zainstaluj puppeteer w katalogu głównym aplikacji, zamiast polegać na npm_module_path. Ten klucz konfiguracji odpowiada setNodeModulePath() Browsershot, które generuje prefiks POSIX NODE_PATH=… i nie działa na Windows — Node rozwiązuje tam moduły, przechodząc w górę katalogów od aplikacji, więc działa instalacja w jej katalogu głównym. Zobacz Ograniczenia.

Włączanie ​

Opublikuj konfigurację, jeśli jeszcze tego nie zrobiłeś (php artisan vendor:publish --tag=seo-config), i włącz opcję:

php
// config/seo.php
'og_image' => [
    'enabled' => true,   // requires spatie/browsershot + Chrome
],

Następnie wygeneruj karty z wyprzedzeniem (dopóki tego nie zrobisz, nic nie jest renderowane):

bash
php artisan seo:og-images

Jak działa rozstrzyganie wartości ​

Generowanie nigdy nie nadpisuje ustawionego przez Ciebie obrazu. Gdy funkcja jest włączona, resolver uzupełnia og:image tylko wtedy, gdy strona nie ma własnego obrazu — czyli gdy rozstrzygnięte og:image jest puste lub nadal wskazuje wspólne statyczne default_og_image witryny. Jawny obraz modelu (z getSEOImage(), rekordu seo_meta, pola treści, …) zawsze ma pierwszeństwo przed wygenerowaną kartą.

Aby ustalić wartość, resolver wywołuje wyszukiwanie generatora uzależnione od istnienia pliku: oblicza ścieżkę karty w magazynie i zwraca jej publiczny URL tylko wtedy, gdy plik już istnieje na skonfigurowanym dysku. Nigdy nie renderuje. Na tym opiera się zabezpieczenie:

  • Żądanie WWW nigdy nie uruchamia przeglądarki — w najgorszym razie wskazuje statyczne default_og_image, dokładnie tak jak przed dodaniem funkcji.
  • Strona nigdy nie odsyła do niewygenerowanego obrazu, więc nie ma okresu, w którym udostępnienia wskazują 404.

Lukę między „treść się zmieniła” a „karta istnieje” zamykasz, uruchamiając polecenie seo:og-images — przy wdrożeniu lub zgodnie z harmonogramem.

Polecenie seo:og-images ​

Generuje karty z wyprzedzeniem, aby resolver miał co udostępnić.

bash
php artisan seo:og-images                         # warm the configured models
php artisan seo:og-images --model="App\Models\Post"
php artisan seo:og-images --force                 # re-render even existing cards
php artisan seo:og-images --prune                 # + delete orphaned cards
  • --model=* — jedna lub kilka klas modeli, dla których przygotować karty. Można powtarzać. Po pominięciu polecenie korzysta z seo.og_image.models, a w razie braku — z modeli mapy witryny (seo.sitemap.models), zgodnie z tym samym podejściem współdzielenia źródeł mapy witryny co seo:llms-txt.
  • --force — ponownie renderuje istniejące karty (użyj po zmianie szablonu lub kolorów marki bez zwiększenia cache_version).
  • --prune — po przygotowaniu kart usuwa zapisane karty ze skonfigurowanej ścieżki, które nie odpowiadają już treści żadnego bieżącego modelu (zobacz poniżej). Dla bezpieczeństwa usuwa tylko pliki o nazwach będących wygenerowanymi skrótami treści (nigdy innych zasobów w tym samym katalogu) i jest ignorowane przy uruchomieniu ograniczonym przez --model (którego zestaw zachowywanych plików nie obejmowałby innych modeli) — uruchom bez --model.

Każdy model musi używać traitu HasSEO. Rekord bez tytułu jest pomijany (nie ma czego umieścić na karcie); polecenie raportuje liczby generated, skipped, failed oraz (z --prune) pruned.

Harmonogram ​

Przygotowuj karty zgodnie z harmonogramem, aby nadążały za treścią, i usuwaj osierocone pliki pozostające po zmianach tytułów:

php
// routes/console.php
Schedule::command('seo:og-images --prune')->daily();

Model unieważniania pamięci podręcznej ​

Nazwa pliku karty jest skrótem wszystkiego, co wpływa na jej piksele — tytułu, nazwy witryny, nazwy szablonu, sterownika, wymiarów, kolorów gradientu marki, numeru cache_version i zainstalowanej wersji pakietu.

Ten skrót jest kluczem pamięci podręcznej i ma dwie konsekwencje, które warto rozumieć:

  • Zmiana tytułu → nowy skrót → nowy plik. Stara karta staje się osieroconym plikiem na dysku, a strona wraca do statycznej wartości domyślnej, dopóki ponownie nie przygotujesz kart. Uruchomienie polecenia generuje nową kartę; --prune usuwa osieroconą. Tak działa unieważnianie — nie ma osobnego kroku „wyczyść jedną stronę”.
  • Zwiększenie cache_version lub aktualizacja pakietu → zmiana wszystkich skrótów. Użyj cache_version po edycji szablonu lub kolorów marki, aby unieważnić wszystkie karty naraz; aktualizacja pakietu jest uwzględniana automatycznie, więc nowe wydanie zmieniające dołączony szablon nie może udostępniać nieaktualnych kart.

Dołączone szablony ​

Pakiet zawiera trzy szablony, wszystkie 1200×630 z tym samym gradientem marki:

SzablonNajlepszy doPokazuje
seo::og.defaultDowolnej treściTytuł + nazwa witryny
seo::og.articleWpisów blogowych, wiadomościNadtytuł sekcji + tytuł + podpis autor · data
seo::og.productProduktów, ogłoszeńZnak marki + etykieta kategorii + tytuł + opis

Wybierz jeden globalnie przez seo.og_image.template lub mapuj szablony według typu modelu, aby artykuł i produkt automatycznie otrzymywały różne karty:

php
// config/seo.php
'og_image' => [
    'templates' => [
        App\Models\Post::class    => 'seo::og.article',
        App\Models\Product::class => 'seo::og.product',
    ],
],

Model może też nadpisać własny szablon podczas działania, definiując getOgImageTemplate(): ?string (zwróć nazwę widoku albo null, aby użyć mapy/wartości domyślnej). Pierwszeństwo: hook modelu, następnie mapa templates, a potem globalne template.

Dostosowywanie szablonu ​

Karta jest widokiem Blade (domyślnie seo::og.default) renderowanym do samowystarczalnego dokumentu HTML — dołączony font jest osadzony jako data URI, więc przeglądarka nie potrzebuje sieci. Są dwa sposoby zmiany:

Opublikuj i edytuj dołączony widok:

bash
php artisan vendor:publish --tag=seo-views

Następnie edytuj resources/views/vendor/seo/og/default.blade.php.

Lub wskaż własny widok:

php
// config/seo.php
'og_image' => [
    'template' => 'og.my-card',   // resources/views/og/my-card.blade.php
],

Szablon otrzymuje te zmienne:

ZmiennaTypUwagi
$titlestringTytuł OG, jeśli ustawiony, w przeciwnym razie tytuł strony.
$siteName?stringRozstrzygnięte og:site_name.
$fontDataUristringDołączony pogrubiony font jako URI data: (pusty ciąg, jeśli niedostępny — przeglądarka używa wtedy własnego fontu bezszeryfowego).
$gradientFromstringseo.og_image.gradient_from.
$gradientTostringseo.og_image.gradient_to.
$widthintSzerokość wyniku (domyślnie 1200).
$heightintWysokość wyniku (domyślnie 630).
$locale?stringRozstrzygnięte ustawienia regionalne strony dla atrybutu <html lang>.
$author?stringAutor artykułu (używany przez seo::og.article).
$publishedDate?stringData publikacji dla seo::og.article: średni format ICU w ustawieniach regionalnych strony, gdy dostępny; w przeciwnym razie Carbon tłumaczy miesiąc w kolejności M j, Y. Null, gdy nie podano daty.
$section?stringSekcja / kategoria treści (nadtytuł artykułu, etykieta produktu).
$description?stringOpis OG, w przeciwnym razie opis strony (używany przez seo::og.product).

Nazwa szablonu jest częścią klucza pamięci podręcznej

Zarówno nazwa szablonu, jak i kolory gradientu wchodzą do skrótu treści, więc zmiana szablonu lub kolorów automatycznie unieważnia istniejące karty. Edycja szablonu w miejscu tego nie robi (nazwa się nie zmienia) — po edycji zwiększ cache_version (lub uruchom --force).

Konfiguracja ​

php
// config/seo.php
'og_image' => [
    'enabled' => false,             // master switch (off by default)
    'driver'  => 'browsershot',     // the render driver; register your own via OgImageManager::extend()
    'template' => 'seo::og.default', // the default Blade view rendered as the card
    'templates' => [],              // per-model-class template overrides (see "Bundled templates")
    'strip_title_suffix' => true,   // trim seo.title_suffix off the card title (the card shows the site name itself)

    'width'  => 1200,               // social-card standard
    'height' => 630,

    'disk' => 'public',             // must be publicly served — its url() becomes the og:image
    'path' => 'og-images',          // path prefix on that disk

    // Models seo:og-images warms. Empty → falls back to seo.sitemap.models.
    // Accepts a list [Post::class] or a map [Post::class => [...]].
    'models' => [],

    // Bump to invalidate every card after editing a template/colors in place.
    // The installed package version is folded in too, so an upgrade busts them.
    'cache_version' => 1,

    // Brand gradient (diagonal) for the bundled default template.
    'gradient_from' => '#1e2a5a',
    'gradient_to'   => '#3D5AFE',

    // Browsershot binary paths. null = its defaults (node/npx on PATH,
    // puppeteer's bundled Chromium). Set explicitly in production.
    'chrome_path'     => null,      // path to a system Chrome/Chromium
    'node_binary'     => null,      // path to the node binary
    'npm_module_path' => null,      // node_modules dir (no-op on Windows — see Caveats)

    'timeout' => 60,                // hard per-render timeout, seconds

    // Launch Chrome with --no-sandbox; weakens browser isolation.
    // Prefer configuring the host to support Chrome's sandbox (see below).
    'no_sandbox' => false,

    // Extra Chromium CLI flags, e.g. ['disable-dev-shm-usage', 'disable-gpu']
    // on a low-/dev-shm container. Leading "--" optional; map form for
    // value-bearing flags: ['proxy-server' => 'http://…'].
    'browsershot_args' => [],

    // Fallback font families for glyphs the bundled face lacks (CJK, Thai,
    // Arabic, …). null = the built-in Noto list; see "Fonts and non-Latin
    // scripts" below.
    'font_stack' => null,
],

Większość wartości skalarnych ma odpowiadającą zmienną środowiskową (SEO_OG_IMAGE_ENABLED, SEO_OG_IMAGE_DISK, SEO_OG_IMAGE_CHROME_PATH, SEO_OG_IMAGE_NO_SANDBOX, …) — pełną listę znajdziesz w pliku konfiguracji. Klucze tablicowe (templates, models, browsershot_args, font_stack) edytuje się bezpośrednio w pliku konfiguracji.

Dysk musi być publicznie udostępniany, ponieważ resolver używa jego url() jako wartości og:image. Dla dysku public uruchom raz php artisan storage:link, aby public/storage na niego wskazywało.

Uruchamianie w systemie Linux (sandbox) ​

Na hostach ograniczających mechanizmy sandboxa Chrome php artisan seo:og-images może zakończyć się błędem:

No usable sandbox! Update your OS ... or see
https://chromium.googlesource.com/.../linux/suid_sandbox_development.md

Jedną z możliwych przyczyn są ograniczone przestrzenie nazw użytkownika w Ubuntu 23.10+. Sprawdź przewodnik rozwiązywania problemów Puppeteer i rzeczywisty błąd uruchomienia przeglądarki. Preferuj naprawę konfiguracji hosta, aby Chrome mógł zachować sandbox.

1. Jawne rozwiązanie awaryjne: uruchom Chrome z --no-sandbox. Wyłącza to izolację przeglądarki. Używaj tylko wtedy, gdy świadomie akceptujesz ten kompromis we wdrożeniu:

php
// config/seo.php
'og_image' => [
    'no_sandbox' => true,   // or set SEO_OG_IMAGE_NO_SANDBOX=true
],

Rankbeam renderuje statyczny wygenerowany HTML i blokuje żądania zdalnych zasobów, ale te mechanizmy nie zastępują sandboxa Chrome. Uruchamiaj proces renderowania bez podwyższonych uprawnień i izoluj go od niezwiązanych zadań i sekretów.

2. Zachowaj sandbox. Pozostaw no_sandbox wyłączone. Gdy przyczyną jest AppArmor, dostosuj profil do konkretnego pliku wykonywalnego Chrome; zobacz wskazówki Chromium. Na przykład:

# /etc/apparmor.d/chrome-og
abi <abi/4.0>,
include <tunables/global>
profile chrome-og /path/to/chrome flags=(unconfined) {
  userns,
  include if exists <local/chrome-og>
}

Następnie załaduj profil przez sudo apparmor_parser -r /etc/apparmor.d/chrome-og i sprawdź, czy Chrome uruchamia się z włączonym sandboxem.

Inne flagi

Dla kontenera z małą ilością pamięci współdzielonej (drugi częsty problem na Linux — awaria Chrome podczas renderowania) dodaj flagi przez browsershot_args:

php
'browsershot_args' => ['disable-dev-shm-usage'],

Własne sterowniki ​

browsershot jest jedynym dołączonym sterownikiem, ale renderer implementuje kontrakt (Rankbeam\Seo\Contracts\OgImageRenderer). Zarejestruj własny — np. renderer oparty na canvas lub usłudze — i wybierz go przez seo.og_image.driver:

php
use Rankbeam\Seo\Services\OgImage\OgImageManager;

app(OgImageManager::class)->extend('my-driver', fn ($app) => new MyRenderer());

Sterownik wyłącznie zamienia samowystarczalny ciąg HTML na bajty PNG o podanym rozmiarze; nie odpowiada za układ ani szablony.

Fonty i pisma inne niż łacińskie ​

Dołączony font kart (Noto Sans Bold, OFL) obsługuje pismo łacińskie, cyrylicę i grekę. Każde inne pismo — chińskie, japońskie, koreańskie, tajskie, arabskie, hebrajskie, dewanagari, emoji — pochodzi z fontów zainstalowanych na maszynie uruchamiającej seo:og-images. Celowo nie dołączono innych: jeden font CJK ma ponad 16 MB, a własny mechanizm zastępczych fontów Chrome dla każdego znaku działa poprawnie, gdy tylko odpowiedni font istnieje na hoście.

Trzy rzeczy zapewniają niezawodność (3.15):

  1. Stos font-family według pisma w każdym dołączonym szablonie. Body deklaruje najpierw 'OGBrand' (dołączony krój), potem seo.og_image.font_stack — domyślnie Noto Sans, cztery rodziny Noto Sans CJK, Noto Sans Thai, Noto Sans Arabic, Noto Sans Hebrew, Noto Sans Devanagari, Noto Color Emoji — a następnie sans-serif. Chrome wybiera zastępczo dla każdego znaku pierwszą zainstalowaną rodzinę, więc lista tylko pomaga; brakująca rodzina jest pomijana. Rodzina CJK języka strony jest przesuwana na początek (ja → JP, zh-Hans → SC, zh-Hant / zh-TW / zh-HK → TC, ko → KR), ponieważ ten sam punkt kodowy Han jest rysowany inaczej w każdym foncie narodowym (unifikacja Han), a atrybut <html lang> zawiera ustawienia regionalne strony w formie BCP 47. Stos jest częścią klucza pamięci podręcznej, więc jego zmiana ponownie renderuje każdą kartę.

  2. Kontrola wstępna w seo:og-images. Przed renderowaniem polecenie pyta fontconfig (fc-list :lang=ja, th, ar, …), czy font obsługuje pisma w tytule, nazwie witryny i opisie, w tym mniej licznie reprezentowane pismo w tekście mieszanym, i ostrzega raz na system pisma, wskazując pakiet do zainstalowania:

    No installed font covers cjk text — its cards may render as boxes. Install one: apt-get install fonts-noto-cjk

    Tam, gdzie nie ma fontconfig (Windows, macOS, minimalny kontener), nie zgłasza niczego, zamiast zgadywać. Samo renderowanie nigdy nie kończy się błędem z powodu brakującego fontu — Chrome rysuje prostokąty .notdef — i właśnie dlatego istnieje ostrzeżenie.

  3. Próbka glifów dla każdego pisma w rzeczywistym teście dymnym. Z SEO_OG_IMAGE_LIVE_TEST=1, tests/Feature/OgImage/BrowsershotSmokeTest.php renderuje tytuł w ja, zh-Hans, zh-Hant, ko, el, ru, tr, th, ar, he i hi obok próbki kontrolnej tej samej długości złożonej z nieprzypisanego punktu kodowego (gwarantowane prostokąty) i zgłasza błąd z nazwą pisma i pakietu, gdy oba PNG są bajtowo identyczne. To test dymny, a nie dowód obsługi każdego glifu: mieszany tekst łaciński lub inne zawijanie mogą powodować różnice obrazów nawet przy brakujących glifach. Sprawdź rzeczywisty render i fonty używane na hoście wdrożeniowym. Ostrzeżenie FontProbe dla języka również jest kontrolą wstępną, a nie pełnym certyfikatem pokrycia fontów. W rdzeniu nie ma polecenia seo:doctor; do tej kontroli użyj seo:og-images.

Na Debian/Ubuntu:

bash
apt-get install fonts-noto-cjk fonts-noto-core fonts-noto-color-emoji
fc-cache -f

Własne szablony opublikowane przez --tag=seo-views przed 3.15 nadal działają: otrzymują nowe zmienne $fontFamily i $lang i mogą je ignorować.

Ograniczenia ​

Opisane wprost, ponieważ powodują problemy na produkcji:

  • Wyłącznie generowanie z wyprzedzeniem — brak endpointu renderowania na żywo (v1). Nie ma trasy renderującej kartę na żądanie. Ponieważ nic nie jest renderowane podczas żądania WWW, nie ma powierzchni podpisanych URL-i / SSRF / DoS do skonfigurowania ani ochrony — kompromisem jest konieczność uruchamiania seo:og-images (przy wdrożeniu lub zgodnie z harmonogramem), aby karty istniały.
  • npm_module_path nie działa na Windows. Odpowiada setNodeModulePath() Browsershot, które poprzedza polecenie prefiksem POSIX NODE_PATH=… — ignorowanym przez Windows. Na Windows zainstaluj puppeteer w katalogu głównym aplikacji, aby Node odnalazł go, przechodząc w górę katalogów. (Na Linux/macOS ustawienie działa zgodnie z oczekiwaniami).
  • Pisma inne niż łacińskie wymagają fontu na hoście. Zobacz Fonty i pisma inne niż łacińskie poniżej: dołączony font obsługuje pismo łacińskie, cyrylicę i grekę; pozostałe pochodzą z fontów zainstalowanych w obrazie wdrożeniowym, a polecenie informuje o brakach.
  • Błąd nie blokuje działania strony. Jeśli renderowanie nie powiedzie się (brak pakietu, awaria przeglądarki, przekroczenie czasu), polecenie to zgłasza, a strona po prostu zachowuje statyczne default_og_image — uszkodzona przeglądarka nigdy nie powoduje błędu 500 strony.

rankbeam/laravel-seo jest udostępniany na licencji MIT.