Bu yazı, abdulazizakyol.com’un eski bir WordPress kurulumundan Astro 5 ile üretilen tamamen statik bir siteye taşınmasının ve ardından yapılan Core Web Vitals çalışmasının vaka çalışmasıdır. Mobil Lighthouse ölçümünde ana sayfanın performans puanı 62’den 98’e çıktı; en büyük içerikli boyama (Largest Contentful Paint, LCP) 16,8 saniyeden 1,9 saniyeye, sayfa ağırlığı 2.975 KiB’den 309 KiB’a indi.
Aşağıda her tekniği “neden, nasıl, hangi kod” sırasıyla anlatıyorum. Kod parçaları sitenin gerçek deposundan alındı; kısalttığım yerleri belirttim.
Sorun neydi?
Kişisel bir sitede WordPress’in asıl maliyeti barındırma ücreti değil, bakım yüküdür: çekirdek, tema ve eklenti güncellemeleri, her eklentinin getirdiği ayrı güvenlik riski ve bir eklenti kaldırıldığında bozulan sayfalar. İçeriği sık değişmeyen bir site için bu yük orantısız.
İkinci sorun hızdı. Mobilde ana sayfa yaklaşık 3 MB veri indiriyordu ve en büyük görsel ekrana 16,8 saniyede geliyordu. Mobil ağda bu, ziyaretçinin uzun süre boş ya da yarım bir sayfaya bakması demek.
Karar: sunucuda hiçbir şey çalıştırmayan statik bir site. Astro 5 her sayfayı derleme sırasında HTML’e çeviriyor, çıktı Cloudflare Pages’e yükleniyor. PHP, veritabanı ve yönetim paneli yok; saldırılacak bir giriş formu ya da güncellenmesi unutulacak bir eklenti de yok. Yazılar depoda Markdown dosyası olarak duruyor.
Nasıl ölçtük?
Tüm ölçümler canlı sitede, Lighthouse’un mobil profiliyle (simüle 4G) yapıldı. “Önce” değerleri eski WordPress sitesinden, “sonra” değerleri yeni siteden. İki uyarı önemli:
- Lighthouse puanı bir aralıktır. Ana sayfa ölçümler arasında 87 ile 98 arasında dalgalandı. Chrome ekibi de puanı tek bir sayı yerine dağılım olarak düşünmeyi öneriyor (Lighthouse performance scoring).
- Lighthouse bir laboratuvar ölçümüdür. Core Web Vitals’ın asıl değerlendirmesi gerçek kullanıcı verisinin 75. yüzdelik dilimi üzerinden yapılır: LCP ≤ 2,5 sn, Interaction to Next Paint (INP) ≤ 200 ms, Cumulative Layout Shift (CLS) ≤ 0,1 (web.dev: Web Vitals). Lighthouse sayfa yüklemesinde INP ölçmez; ona en yakın laboratuvar göstergesi Total Blocking Time’dır (TBT).
Lighthouse 10 ile performans puanındaki ağırlıklar şöyle: TBT %30, LCP %25, CLS %25, First Contentful Paint ve Speed Index %10’ar. LCP ve CLS birlikte puanın yarısını oluşturuyor; bu yüzden görsellerle uğraşmak en çok getiren iş oldu.
Yapılanlar
1. Görseller: derleme sonunda WebP varyantları
Neden: Sayfa ağırlığının büyük kısmı fotoğraflardı. Telefona 1920 px genişliğinde bir JPEG göndermek, ekranın kullanabileceğinin birkaç katı veri demek. Astro’nun Image ve Picture bileşenleri yalnızca src/ altındaki görselleri işler; public/ içindekiler olduğu gibi kopyalanır. Eski WordPress adreslerini (/uploads/2020/07/...) bozmamak için görseller public/uploads altında kaldı, bu yüzden kendi adımımızı yazdık.
Nasıl: Derleme bittiğinde çalışan küçük bir Astro entegrasyonu (astro:build:done kancası), sharp ile her JPEG/PNG’den 360, 540, 720, 960, 1280, 1600 ve 1920 px genişliğinde WebP varyantları üretiyor; kaynaktan büyük varyant üretilmiyor. Çıktılar node_modules/.cache altında saklanıyor: 83 görsel ilk derlemede yaklaşık 5 saniyede işleniyor, sonraki derlemelerde önbellekten anında kopyalanıyor.
Kurallar tek dosyada; entegrasyon ve bileşenler aynı listeyi kullanıyor:
// src/lib/responsive.mjs
// /uploads/2020/07/foto.jpeg → /_img/2020/07/foto-640.webp, -960.webp, ...
export const WIDTHS = [360, 540, 720, 960, 1280, 1600, 1920];
export const QUALITY = 72;
export const isOptimizable = (src) => typeof src === 'string' && /^\/uploads\/.+\.(jpe?g|png)$/i.test(src);
/** Orijinal genişliğe göre üretilecek varyant genişlikleri (büyütme yapılmaz). */
export function variantWidths(originalWidth) {
const ws = WIDTHS.filter((w) => w < originalWidth);
ws.push(Math.min(originalWidth, WIDTHS[WIDTHS.length - 1]));
return [...new Set(ws)];
}
export const variantPath = (src, w) => src.replace(/^\/uploads\//, '/_img/').replace(/\.(jpe?g|png)$/i, `-${w}.webp`);
export const srcsetFor = (src, originalWidth) =>
variantWidths(originalWidth).map((w) => `${encodeURI(variantPath(src, w))} ${w}w`).join(', ');
Varyant üretimi, altı paralel işle (kısaltılmış):
// integrations/responsive-images.mjs — buildVariants() içinden
for (const w of variantWidths(width)) {
const rel = variantPath(src, w).slice(1);
const cached = join(CACHE, rel), out = join(root, rel);
if (!(await newer(cached, orig))) { // önbellekte yoksa ya da kaynak daha yeniyse üret
await mkdir(dirname(cached), { recursive: true });
await sharp(orig).resize({ width: w, withoutEnlargement: true })
.webp({ quality: QUALITY, effort: 5 }).toFile(cached);
}
await mkdir(dirname(out), { recursive: true });
await copyFile(cached, out); // dist/_img/... altına kopyala
}
Bileşen tarafında responsive(src, sizes) yardımcısı srcset, sizes, width ve height döndürüyor. Boyutların HTML’de olması, görsel inmeden yerinin ayrılması, yani CLS’nin 0’da kalması demek:
// src/lib/img.ts — kullanım: <img {...responsive(src, '(max-width: 560px) 100vw, 400px')} />
export function responsive(src: string, sizes: string) {
const d = dims(src); // JPEG/PNG başlığından genişlik ve yükseklik
if (!d || !isOptimizable(src)) return { src, ...d };
return { src, srcset: srcsetFor(src, d.width), sizes, width: d.width, height: d.height };
}
2. Yazı içindeki görsellere otomatik srcset
Neden: Blog yazıları Markdown ve eski HTML içeriyor; her <img> etiketini elle düzenlemek sürdürülebilir değil.
Nasıl: Aynı entegrasyon üretilen her HTML dosyasını tarıyor ve srcset’i olmayan /uploads/ görsellerine srcset, sizes, boyut, decoding="async" ve loading="lazy" ekliyor. fetchpriority verilmiş, yani LCP adayı olan görsellere lazy eklenmiyor:
// integrations/responsive-images.mjs
const PROSE_SIZES = '(max-width: 820px) calc(100vw - 32px), 760px';
function rewriteImg(tag, meta) {
const src = attr(tag, 'src');
if (!src || attr(tag, 'srcset') !== undefined) return tag;
const key = decodeURI(src);
const m = meta.get(key);
if (!m) return tag;
const add = [];
add.push(`srcset="${srcsetFor(key, m.width)}"`);
if (attr(tag, 'sizes') === undefined) {
const w = Number(attr(tag, 'width'));
add.push(`sizes="${w && w <= 480 ? `${w}px` : PROSE_SIZES}"`);
}
if (attr(tag, 'width') === undefined && attr(tag, 'height') === undefined) add.push(`width="${m.width}" height="${m.height}"`);
if (attr(tag, 'decoding') === undefined) add.push('decoding="async"');
if (attr(tag, 'loading') === undefined && attr(tag, 'fetchpriority') === undefined) add.push('loading="lazy"');
return tag.replace(/^<img/, `<img ${add.join(' ')}`);
}
3. LCP görseli: background-image yerine img ve preload
Neden: Hero ve sayfa başlığı görselleri CSS background-image olarak veriliyordu. Tarayıcının ön yükleme tarayıcısı (preload scanner) HTML’i okurken CSS içindeki görseli göremez; görsel ancak stil hesaplandıktan sonra istenir. web.dev’in LCP rehberi bu bekleyişi “kaynak yükleme gecikmesi” (resource load delay) olarak ayrı bir kalemde ele alıyor ve LCP görselinin asla lazy-load edilmemesini öneriyor.
Nasıl: Görsel artık gerçek bir <img>: fetchpriority="high", srcset ve sizes ile. <head> içinde de imagesrcset’li bir preload var; tarayıcı hangi genişliği indireceğine HTML’in başında karar veriyor:
---
// src/layouts/Base.astro (ilgili satırlar)
// Sayfa başlığı görseli: iç sayfalarda LCP öğesi. <img> + preload ile erken keşfedilir.
const bannerImg = !hero && banner ? responsive(banner, '100vw') : undefined;
---
<head>
<link rel="preload" href="/fonts/manrope-latin.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/big-shoulders-display-latin.woff2" as="font" type="font/woff2" crossorigin />
{bannerImg?.srcset && <link rel="preload" as="image" imagesrcset={bannerImg.srcset} imagesizes="100vw" fetchpriority="high" />}
</head>
<body>
<header class="page-head">
{bannerImg && <img class="bg" {...bannerImg} alt="" fetchpriority="high" decoding="async" />}
</header>
</body>
Ana sayfa hero’sunda görsel dikey ekranda genişliği değil yüksekliği doldurduğu için sizes değeri (orientation: portrait) 75vh, 100vw. Bu ayrıntı küçük görünür ama telefonda tarayıcının doğru varyantı seçmesini sağlar.
4. Diğer hero slaytları: sırası gelince
Neden: Slayt gösterisindeki tüm görseller ilk yüklemede inerse LCP görseliyle bant genişliği için yarışır.
Nasıl: İlk slayt HTML ile geliyor; diğerlerinin srcset’i data-srcset içinde bekliyor ve sayfa yüklendikten sonra, sırası gelmeden hemen önce atanıyor. Veri tasarrufu (Save-Data) açıksa, bağlantı 2G ise ya da kullanıcı hareketin azaltılmasını (prefers-reduced-motion) seçtiyse slaytlar hiç dönmüyor:
// src/views/Home.astro — <script>
const hero = document.querySelector<HTMLElement>('.hero');
const slides = [...document.querySelectorAll<HTMLImageElement>('.slide')];
const conn = (navigator as any).connection;
const quiet = matchMedia('(prefers-reduced-motion: reduce)').matches || conn?.saveData || /(^|-)2g$/.test(conn?.effectiveType ?? '');
if (hero && slides.length > 1 && !quiet) {
const every = Number(hero.dataset.interval) || 5000;
const load = (img: HTMLImageElement) => {
if (img.dataset.srcset) { img.srcset = img.dataset.srcset; delete img.dataset.srcset; }
return img.decode().catch(() => {});
};
let i = 0;
const tick = async () => {
if (document.hidden) return;
const n = (i + 1) % slides.length;
await load(slides[n]);
slides[i].classList.remove('active'); slides[n].classList.add('active'); i = n;
load(slides[(n + 1) % slides.length]);
};
addEventListener('load', () => { setTimeout(() => load(slides[1]), every - 1500); setInterval(tick, every); }, { once: true });
}
5. Fontlar: siteyle birlikte woff2 ve iki preload
Neden: Google Fonts, ayrı bir alan adına bağlantı ve render’ı engelleyen bir CSS isteği demek.
Nasıl: Üç font ailesinin yalnızca latin ve latin-ext alt kümeleri woff2 olarak /fonts altında. Türkçedeki ğ, ş ve İ gibi harfler latin-ext kümesinde; unicode-range sayesinde bu dosya yalnızca gerektiğinde iniyor. İlk ekranda kullanılan iki dosya yukarıdaki Base.astro parçasında preload ediliyor. Font preload’unda crossorigin özniteliği şart; yoksa tarayıcı dosyayı iki kez indirir.
/* src/styles/fonts.css — altı tanımdan biri */
@font-face {
font-family: 'Manrope'; font-style: normal; font-weight: 400 700; font-display: swap;
src: url('/fonts/manrope-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
6. CSS’i sayfaya gömmek
Neden: Harici CSS dosyası render’ı engeller; tarayıcı onu indirmeden sayfayı çizmez.
Nasıl: Astro’da tek ayar: build.inlineStylesheets: 'always'. Varsayılan 'auto' yalnızca küçük stil dosyalarını gömer. Bu sitenin CSS’i küçük olduğu için her sayfaya gömmek ayrı bir istek beklemekten ucuz. CSS’i büyük sitelerde takas tersine dönebilir: aynı CSS her sayfada yeniden iner ve tarayıcı önbelleğinin avantajı kaybolur.
// astro.config.mjs
import { defineConfig } from 'astro/config';
import responsiveImages from './integrations/responsive-images.mjs';
export default defineConfig({
site: 'https://www.abdulazizakyol.com',
// Tüm adresler sonunda "/" ile biter; Cloudflare Pages de bu biçimi sunar.
// Böylece iç bağlantılar 308 yönlendirmesine takılmaz ve canonical tek olur.
trailingSlash: 'always',
// CSS küçük (~13 KB); sayfaya gömülmesi render'ı engelleyen istekleri kaldırır.
build: { format: 'directory', inlineStylesheets: 'always' },
integrations: [responsiveImages()],
});
7. YouTube: tıklanınca yüklenen önizleme
Neden: Gömülü YouTube iframe’i, kullanıcı oynat düğmesine hiç basmasa bile oynatıcının betiklerini ve stillerini indirir.
Nasıl: Sayfada yalnızca önizleme görseli ve oynat simgesi olan bir bağlantı var. Tıklanınca iframe youtube-nocookie.com adresiyle oluşturuluyor; JavaScript çalışmazsa bağlantı videoya YouTube’da açılıyor. Video ve iframe’in temelleri için HTML5 medya etiketleri yazısına bakabilirsiniz.
// src/views/StageItem.astro — <script>
document.querySelectorAll<HTMLAnchorElement>('a.yt').forEach((a) => a.addEventListener('click', (e) => {
e.preventDefault();
const f = document.createElement('iframe');
f.src = `https://www.youtube-nocookie.com/embed/${a.dataset.id}?autoplay=1&rel=0`;
f.title = a.dataset.title || '';
f.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
f.allowFullscreen = true;
f.style.cssText = 'width:100%;height:100%;border:0';
const box = document.createElement('div'); box.className = 'video';
box.append(f);
a.replaceWith(box);
}));
8. Cloudflare e-posta gizleme betiğini kapatmak
Neden: Cloudflare’in e-posta adresi gizleme (Email Address Obfuscation) özelliği, adresleri çözmek için sayfaya ayrı bir betik (email-decode.min.js) ekler. Adresi zaten herkese açık olan bir kişisel sitede bu fazladan bir istektir; üstelik adresi yapay zekâ tarayıcılarından da saklar.
Nasıl: Cloudflare’in belgelediği <!--email_off--> ve <!--/email_off--> yorumları Base.astro içinde tüm gövdeyi sarıyor. Panel ayarına dokunmadan, kodla ve sürüm kontrolünde.
9. Erişilebilirlik ve otomatik mobil taşma testi
Neden: Lighthouse’un erişilebilirlik denetimi düşük kontrastı ve okunması zor küçük yazıları işaretler. Mobilde yatay kaydırma ise kullanıcı deneyimini doğrudan bozar ve gözle aramakla bulunamayacak kadar çok sayfa–genişlik bileşimi vardır.
Nasıl: Kontrastı yetersiz metinler ve 12 px altındaki yazılar düzeltildi. Taşma için puppeteer ile otomatik bir test yazıldı: 19 sayfa, 320–768 px arasında 5 genişlikte açılıyor ve görünüm alanının dışına taşan öğeler listeleniyor. İlk çalıştırmada 51 sorun çıktı; uzun URL ve kod satırlarının kırılması, başlıkların satır kırabilmesi gibi düzeltmelerle sayı 0’a indi. Anlamsal HTML’in erişilebilirliğe katkısı için HTML5 anlamsal web yazısı iyi bir başlangıç. Testin basitleştirilmiş hâli:
// mobil-tasma-testi.mjs — önce: npm i -D puppeteer; site yerelde çalışırken: node mobil-tasma-testi.mjs
import puppeteer from 'puppeteer';
const BASE = 'http://localhost:4321';
const PAGES = ['/', '/hakkimda/', '/blog/', '/en/']; // kendi sayfa listeniz
const WIDTHS = [320, 360, 390, 414, 768]; // örnek genişlikler
const browser = await puppeteer.launch();
const page = await browser.newPage();
let problems = 0;
for (const path of PAGES) {
for (const width of WIDTHS) {
await page.setViewport({ width, height: 800 });
await page.goto(BASE + path, { waitUntil: 'load' });
const wide = await page.evaluate(() => {
const vw = document.documentElement.clientWidth;
if (document.documentElement.scrollWidth <= vw) return []; // yatay kaydırma yok
return [...document.querySelectorAll('body *')]
.filter((el) => el.getBoundingClientRect().right > vw + 1)
.slice(0, 5)
.map((el) => el.tagName.toLowerCase() + (typeof el.className === 'string' && el.className.trim() ? '.' + el.className.trim().split(/\s+/).join('.') : ''));
});
if (wide.length) { problems += wide.length; console.log(`${path} @ ${width}px →`, wide.join(', ')); }
}
}
await browser.close();
console.log(problems ? `${problems} taşan öğe` : 'Taşma yok');
process.exit(problems ? 1 : 0);
10. Önbellek başlıkları
Cloudflare Pages, public/_headers dosyasındaki kuralları HTTP başlığı olarak uyguluyor (Cloudflare Pages: Headers). Adı değişmeden içeriği değişmeyecek dosyalar (fontlar, WebP varyantları, Astro’nun adında içerik özeti (hash) taşıyan dosyaları) bir yıl ve immutable olarak önbelleğe alınıyor; tekrar ziyarette bu dosyalar için hiç istek atılmıyor. Dosyanın ilgili kısmı:
/fonts/*
Cache-Control: public, max-age=31536000, immutable
Access-Control-Allow-Origin: *
/_img/*
Cache-Control: public, max-age=31536000, immutable
/_astro/*
Cache-Control: public, max-age=31536000, immutable
SEO ve GEO: hız dışında neler yapıldı?
Hızlı bir sayfa bulunamıyorsa işe yaramaz. Aynı taşımada şunlar da yapıldı:
- Yapısal veri: Her sayfada Person, Organization ve WebSite düğümlerinden oluşan tek bir JSON-LD grafı var; sayfaya özel düğümler (BlogPosting, ProfilePage vb.) bunlara
@idile bağlanıyor. Arama motorlarının ve yapay zekâ asistanlarının kişiyi ve şirketi tek bir varlık olarak eşleştirmesi kolaylaşıyor. - hreflang: Türkçe ve İngilizce sayfalar birbirini
tr,envex-defaultile gösteriyor. - llms.txt ve llms-full.txt: llmstxt.org önerisine göre biçimlenmiş, kimliği ve tüm sayfaları listeleyen bir dosya.
llms-full.txttüm yazıların düz metnini tek dosyada veriyor; bu ikincisi önerinin parçası değil, yaygınlaşan bir gelenek. İkisi de derlemede içerikten üretiliyor. - IndexNow: Her yayından sonra site haritasındaki adresler IndexNow’a bildiriliyor. Bing ve Yandex gibi arama motorları bu protokolü kullanıyor; Google kullanmıyor, onun için Search Console’a gönderilen site haritası yeterli.
- 301 yönlendirmeleri: Eski WordPress adresleri (kökteki yazı adresleri,
/category/,/feed/,/portfolios/)public/_redirectsile yeni adreslere kalıcı olarak yönleniyor. Dosya her derlemeden önce bir betikle üretiliyor; yeni yazı eklendiğinde unutulmuyor.
llms.txt bir Astro uç noktası olarak üretiliyor (kısaltılmış):
// src/pages/llms.txt.ts — derlemede /llms.txt dosyasına yazılır
import site from '../data/site.json';
import { allPosts } from '../lib/blog';
import { iso } from '../lib/util';
export async function GET() {
const posts = await allPosts('tr');
const u = (p: string) => `${site.url}${p}`;
const lines = [
`# ${site.name}`,
'',
`> ${site.description}`,
'',
'## Blog yazıları (Türkçe) / Blog posts (Turkish)',
'',
...posts.map((p) => `- [${p.data.title}](${u(`/blog/${p.id}/`)}) – ${iso(p.data.date)}${p.data.description ? `: ${p.data.description}` : ''}`),
'',
];
return new Response(lines.join('\n'), { headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
}
Sonuç: önce ve sonra
Mobil Lighthouse, canlı site, simüle 4G:
| Sayfa | Performans | LCP | Sayfa ağırlığı |
|---|---|---|---|
| Ana sayfa (TR) | 62 → 98 | 16,8 sn → 1,9 sn | 2.975 KiB → 309 KiB |
| Hakkımda | 67 → 93 | 9,0 sn → 2,6 sn | 1.278 KiB → 224 KiB |
| Blog yazısı | 70 → 96 | 5,7 sn → 2,4 sn | 1.580 KiB → 298 KiB |
| İngilizce ana sayfa | 67 → 93 | 13,7 sn → 2,7 sn | — |
Masaüstünde tüm sayfalar 99–100 aldı. Erişilebilirlik, En İyi Uygulamalar ve SEO kategorileri 100, CLS 0. Ana sayfanın ağırlığı yaklaşık onda birine indi.
Dürüst not: Hakkımda ve İngilizce ana sayfada laboratuvar LCP’si 2,5 saniyelik eşiğin biraz üzerinde; iyileştirme payı hâlâ var. Benim çıkardığım ders şu: çerçeve değişikliği zemini hazırladı, farkı ise LCP görselini erken keşfettirmek ve gereksiz baytları hiç göndermemek yarattı. Aynı “önce ölç, sonra tek değişken değiştir” yaklaşımını veri tabanı tarafında SQL Server performans ayarı yazısında anlattım.
Kendi sitenizde uygulamak için kontrol listesi
- Önce ölçün: Lighthouse’u mobil profilde birkaç kez çalıştırıp aralığı not edin; varsa Search Console’daki Core Web Vitals raporuna (gerçek kullanıcı verisi) bakın.
- LCP öğesini bulun. Görselse:
<img>,fetchpriority="high", lazy yok,srcset+sizes, gerekirseimagesrcset’li preload. - Her görseli birden çok genişlikte WebP’ye çevirin ve
width/heightverin. - Ekranın altındaki görsellere
loading="lazy"vedecoding="async"ekleyin. - Fontları kendi alan adınızdan woff2 olarak sunun, gereken alt kümeleri seçin, ilk ekrandaki bir iki fontu
crossoriginile preload edin. - Render’ı engelleyen CSS’i azaltın; küçükse sayfaya gömün.
- Üçüncü taraf gömmeleri (video, harita, sohbet penceresi) tıklanınca yükleyin.
- CDN’in eklediği betikleri (e-posta gizleme, analiz) gözden geçirin.
- Mobil taşmayı otomatik test edin; kontrastı ve küçük yazıları düzeltin.
- Değişmeyen dosyalara uzun ve
immutableönbellek başlığı verin. - Taşımada eski adresleri 301 ile yönlendirin; yapısal veri, hreflang, site haritası ve llms.txt’yi derlemede otomatik üretin.
Sık sorulan sorular
WordPress'ten Astro'ya geçmek site hızını neden artırır?
Astro sayfaları derleme sırasında düz HTML'e çevirir ve sunucuda her istekte PHP ya da veritabanı çalışmaz; CDN sayfanın tamamını önbellekten sunabilir. Ancak asıl kazanç görseller, fontlar ve üçüncü taraf betikler gibi ağır kaynakların tek tek ele alınmasından gelir. Çerçeve değişikliği zemini hazırlar, tek başına yetmez.
LCP nedir ve iyi bir LCP değeri kaç saniyedir?
Largest Contentful Paint (LCP), sayfadaki en büyük görselin ya da metin bloğunun ekrana çizildiği andır. web.dev'e göre gerçek ziyaretlerin 75. yüzdelik diliminde 2,5 saniye ya da altı iyi kabul edilir. Lighthouse laboratuvarda simüle bir ölçüm yapar; gerçek kullanıcı verisi ayrıca izlenmelidir.
Lighthouse puanı neden her ölçümde farklı çıkıyor?
Ağ yönlendirmesi, sunucu yanıt süresi, ölçüm yapılan makine ve tarayıcı eklentileri gibi dış etkenler sonucu değiştirir. Bu sitenin ana sayfası ölçümler arasında 87 ile 98 arasında dalgalandı. Tek bir sayıya değil, birkaç ölçümün aralığına bakmak daha doğrudur.
Astro'da public klasöründeki görseller neden otomatik optimize edilmiyor?
Astro, public/ altındaki dosyaları işlemeden olduğu gibi kopyalar; Image ve Picture bileşenleri src/ altındaki görseller içindir. Eski WordPress adreslerini korumak için görselleri public/uploads altında bıraktık ve WebP varyantlarını derleme sonunda çalışan küçük bir entegrasyonla sharp kullanarak ürettik.
llms.txt nedir, ne işe yarar?
llms.txt, sitenin kök dizininde duran ve yapay zekâ asistanlarına siteyi Markdown biçiminde özetleyen bir dosyadır: başlık, kısa özet ve önemli sayfaların bağlantı listeleri. llmstxt.org'daki bir öneridir, resmî bir standart ya da tanımlı bir sıralama faktörü değildir; asistanların siteyi doğru anlamasını kolaylaştırır.
Kaynaklar
- Web Vitals (web.dev) web.dev
- Optimize Largest Contentful Paint (web.dev) web.dev
- Lighthouse performance scoring (Chrome for Developers) developer.chrome.com
- Astro Configuration Reference docs.astro.build
- Astro Integration API docs.astro.build
- Cloudflare Pages: Headers developers.cloudflare.com