generateMetadata'yı Unutunca Google Ne Görür
document.title Googlebot'u doyurmuyor

Birisi yazıyı Slack'e yapıştırdı. Önizleme 'Thinote' dedi, açıklama boştu. Tarayıcı sekmesinde doğru başlık vardı. document.title hidrasyondan sonra değişmişti. Slack ve çoğu crawler o JS'i beklemedi. Gördükleri şey root layout'un template'iydi: site adı, yazı adı değil.
Pages Router'da Head vardı, unutulunca da en azından _document bir şey basardı. App Router'da metadata nesnesi veya generateMetadata yoksa ebeveyn kazanır. Ebeveyn 'Thinote' ise her yazı Thinote'dur. Client'ta useEffect ile title set etmek, insanlara doğru, botlara yalan.
Aynı kaynaktan title ve kart
import type { Metadata } from "next";
import { cache } from "react";
import { notFound } from "next/navigation";
import { getArticle } from "@/lib/articles";
const loadArticle = cache(async (slug: string) => getArticle(slug));
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise {
const { slug } = await params;
const article = await loadArticle(slug);
if (!article) return { title: "Yazı bulunamadı" };
const title = article.seoTitle ?? article.title;
const description = article.excerpt.slice(0, 160);
return {
title,
description,
openGraph: {
title,
description,
type: "article",
publishedTime: article.publishedAt.toISOString(),
},
};
}
export default async function ArticlePage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const article = await loadArticle(slug);
if (!article) notFound();
return ;
}
cache() sarmalayıcısı, metadata ile sayfanın aynı sorguyu istek içinde iki kez çalıştırmasını engeller. Bu, üçüncü yazıdaki data cache değil; istek içi dedupe. article.seoTitle yoksa düz başlık. Description'ı 160'ta kesiyorum çünkü OG kartı uzun metni kırpar, ben kırpmazsam kart kırpar.
Layout title'ı yazıyı ezmesin
Root layout'ta title.template kullanmak site adını soneke ekler. Yazıda absolute title vermezsem 'Başlık | Thinote' olur; bu çoğu zaman istediğim. absolute, şablonu devre dışı bırakır. Unuttuğum şey ebeveynin openGraph.title'ını da miras almasıydı. Çocuk yalnızca title set edip OG'yi boş bırakınca Slack yine ebeveyni gösterdi. title ve openGraph.title'ı birlikte döndürmek, o sızıntıyı kesti.
canonical ve robots da burada duruyor. Taslak önizleme rotasına noindex, yayın rotasına canonical. Bunları client'ta eklemek, botun HTML'inde yok demek. generateMetadata, botun indirdiği belgeyi yazar. document.title, sekme etiketi. İkisini karıştırmayınca Slack bir daha site adıyla gelmedi.
İki kez çekmemek
Metadata ve page aynı makaleyi ister. cache() olmazsa iki sorgu, iki kez yetki, iki kez markdown. Deduping fetch URL'lerinde de çalışır; getArticle içeride fetch değil Prisma ise cache() şart. Bunu unutmak, beşinci yazıyı üçüncü yazının faturasına bağlar: bot her yazıda çifte istek üretir. Staging'de fark edilmez, taramada görülür.
json-ld'yi de burada veya sayfada, aynı loadArticle ile basıyorum. Client'ta script inject etmek, Slack'in kaçırdığı başlığın kardeşi. Arama sonuçlarında görünen şey HTML. HTML'i sunucu yazar. Sekmeyi insan görür, kartı bot görür. Kart boşsa generateMetadata eksiktir, React eksik değildir.
Dinamik başlık
Arama sayfasının başlığı query'ye göre değişir. generateMetadata params ve searchParams alır. Client'ta input değişince sekme güncellenmesin diye üzülmüyorum; paylaşılan URL, metadata'nın girdiğidir. URL'de q yoksa başlık 'Arama', varsa 'Arama: next image'. Bunu useEffect ile yazmak, paylaşılmayan bir sekme üretir.
Hata sayfasında da title döndürüyorum. 404'ü 'Thinote' diye bırakmak, tarama raporunda site adından başka bir şey göstermemek. Kısa, dürüst, mutlak. Şablonun sonuna site adı eklemesini istemiyorsam absolute kullanıyorum. Unutulan metadata, unutulan sayfa demek değil; unutulan kart demek.
Open Graph görseli
og:image yoksa kart, site logosuna veya boşluğa düşer. generateMetadata içinde images dizisi, kapak dosyasının mutlak URL'sini ister. Göreli yol, bazı tüketicilerde kırılır. Kapak yoksa varsayılan bir site görseli; yazıya özel yok diye alanı tamamen boş bırakmak, Slack'in tekrar site adına sığınmasına yol açar. Title düzeldi, görsel boş — yarım zafer.
Twitter kartı ayrı nesne. Bazen og yeter, bazen twitter.card summary_large_image ister. İkisini aynı description ile dolduruyorum. Farklı metin yazmak, iki yüzlü bir paylaşım üretir. Bot hangisini seçerse seçsin cümle aynı kalsın. Bu sıkıcılık, beşinci yazının sevdiği sıkıcılık.
Client sayfada metadata yok
page.tsx 'use client' ise generateMetadata aynı dosyada duramaz. Ayırmak gerekir: metadata sunucu dosyasında, sayfa client dosyasında. Çoğu zaman asıl düzeltme sayfayı client yapmamaktır. İkinci yazı. Metadata için ayrı bir layout uydurmak, semptomu evcilleştirmek. Kök neden, gereksiz direktif.
Google'ın gördüğü şey, curl'ün gördüğü şeydir. JS'i çalıştırmadan title'a bakıyorum. Thinote yazıyorsa generateMetadata yok veya ebeveyn kazanmış. Yazı başlığı yazıyorsa kart da büyük ihtimalle doğrudur. Bu kontrolü deploy öncesi bir kez yapmak, Slack screenshot'ından ucuz. Beşinci yazı bu kadar. Sekme yalan söyleyebilir. Belge söylemesin.
Çok dilli başlık
Locale segmentteyse generateMetadata locale'i params'tan okur. Yanlış locale ile doğru slug, yanlış dilde başlık basar. Sözlüğü sunucuda çöz, title'ı o dilde üret. Client'ta i18n provider ile title set etmek, yine Slack'e site adını bırakır. Dil, belgede doğmalı. Sekmede doğması yetmez.
Aynı yazının iki locale'de iki canonical'i olur veya hreflang ile konuşur. Bunları da metadata.alternates içine yazıyorum. Unutunca iki URL aynı başlıkla yarışır. generateMetadata yalnızca title değil, o yazının kimlik kartıdır. Kartı client'ta doldurmak, kartı boş göndermekle aynı kapıya çıkar. Bot, hidrasyonu beklemeye söz vermedi.
Taslak ve noindex
Önizleme rotası yayın rotasıyla aynı generateMetadata'yı paylaşırsa taslak indekslenir. robots: { index: false } taslakta zorunlu. Canonical yayın URL'sine baksın. Unutulan noindex, beşinci yazının Slack ticket'ından daha geç fark edilir. Fark edilince de utanç daha uzun sürer. Title doğru, indeks yanlış — yine yarım belge.
Metadata'yı bir kez yazıp unutmuyorum. Yeni bir dinamik rota açınca ilk baktığım şey generateMetadata'nın durup durmadığı. Durmuyorsa Slack yine site adını basar. Duruyorsa curl yeter. Beşinci yazı bu alışkanlık. Alışkanlık, document.title'dan daha yakın durur.
Yorumlar
Yorumlar (0)
Yorumlar üyelere açık. Üye ol · Giriş yap