Tối ưu Core Web Vitals với Astro: Hướng dẫn đạt điểm 100
Hướng dẫn chi tiết cách tối ưu Core Web Vitals (LCP, FID, CLS) trong Astro - Từ cấu hình đến best practices để đạt điểm Lighthouse 100.
Giới thiệu
Core Web Vitals là bộ metrics mà Google sử dụng để đánh giá page experience - và là ranking factor chính thức từ 2021. Với Astro, bạn đã có lợi thế lớn vì framework được thiết kế cho performance.
Tuy nhiên, không phải cứ dùng Astro là tự động đạt điểm 100. Bài viết này sẽ hướng dẫn chi tiết cách tối ưu từng metric để đạt Lighthouse score 100.
Core Web Vitals là gì?
LCP - Largest Contentful Paint
Target: < 2.5 giây
Thời gian để render element lớn nhất visible trong viewport. Thường là:
- Hero image
- Video thumbnail
- Large text block
FID - First Input Delay
Target: < 100ms
Thời gian từ khi user tương tác đầu tiên (click, tap) đến khi browser response. Bị ảnh hưởng bởi:
- JavaScript execution
- Long tasks
- Main thread blocking
CLS - Cumulative Layout Shift
Target: < 0.1
Đo độ ổn định layout - khi elements shift position sau khi render. Gây ra bởi:
- Images không có dimensions
- Ads/embeds load late
- Fonts loading
Astro Advantages cho Core Web Vitals
Zero JavaScript by Default
Typical Astro Page:
- HTML: 50KB
- CSS: 20KB
- JS: 0KB (!)
Typical Next.js Page:
- HTML: 50KB
- CSS: 20KB
- JS: 100KB+
Kết quả: FID gần như không tồn tại vì không có JavaScript block main thread.
Static Generation
// astro.config.mjs
export default defineConfig({
output: 'static', // Pre-render tất cả pages
});
TTFB < 50ms khi serve từ CDN - giúp LCP dramatically.
Tối ưu LCP
1. Preload Hero Image
---
// Layout.astro
const heroImage = Astro.props.image;
---
<head>
{heroImage && (
<link
rel="preload"
as="image"
href={heroImage}
fetchpriority="high"
/>
)}
</head>
2. Sử dụng Astro Image Optimization
---
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
---
<Image
src={heroImage}
alt="Hero image"
width={1200}
height={630}
format="webp"
quality={80}
loading="eager"
decoding="async"
/>
Best practices:
loading="eager"cho above-fold imagesformat="webp"giảm 30% size- Always specify width/height
3. Critical CSS Inline
---
// Layout.astro
---
<head>
<style is:inline>
/* Critical CSS - above the fold */
body { margin: 0; font-family: system-ui; }
.hero { min-height: 100vh; }
.hero-text { font-size: 3rem; }
</style>
<!-- Non-critical CSS loaded async -->
<link rel="preload" href="/styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
</head>
4. Font Optimization
<head>
<!-- Preconnect to font origin -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Load font with display=swap -->
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap"
rel="stylesheet"
/>
</head>
<style>
body {
font-family: 'Inter', system-ui, sans-serif;
}
</style>
Hoặc self-host fonts:
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Regular.woff2') format('woff2');
font-display: swap;
font-weight: 400;
}
Tối ưu FID
1. Không dùng client:load cho non-essential
---
import Newsletter from './Newsletter.tsx';
import Comments from './Comments.tsx';
---
<!-- ❌ Bad: Load ngay lập tức -->
<Newsletter client:load />
<Comments client:load />
<!-- ✅ Good: Load khi visible -->
<Newsletter client:visible />
<Comments client:visible />
<!-- ✅ Better: Load khi idle -->
<Comments client:idle />
2. Partytown cho Third-party Scripts
Google Analytics, Facebook Pixel - các scripts này block main thread.
npx astro add @astrojs/partytown
---
import { Partytown } from '@astrojs/partytown';
---
<head>
<Partytown config={{ forward: ['dataLayer.push'] }} />
<!-- GA4 chạy trong Web Worker -->
<script type="text/partytown">
// Google Analytics code
</script>
</head>
3. Code Splitting
---
// Heavy component chỉ load khi cần
const HeavyChart = (await import('./HeavyChart.tsx')).default;
---
{showChart && <HeavyChart client:only="react" />}
Tối ưu CLS
1. Always Specify Image Dimensions
<!-- ❌ Bad: No dimensions -->
<img src="/photo.jpg" alt="..." />
<!-- ✅ Good: Explicit dimensions -->
<img src="/photo.jpg" alt="..." width="800" height="600" />
<!-- ✅ Best: Astro Image -->
<Image src={photo} alt="..." width={800} height={600} />
2. Aspect Ratio cho Responsive Images
.image-container {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
3. Reserve Space cho Ads/Embeds
<div class="ad-container" style="min-height: 250px;">
<slot name="ad" />
</div>
<div class="video-container" style="aspect-ratio: 16/9;">
<iframe src="..." loading="lazy"></iframe>
</div>
4. Font Loading Strategy
/* Sử dụng fallback font có metrics tương tự */
body {
font-family: 'Inter',
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
sans-serif;
}
/* Hoặc dùng font-display: optional để prevent FOUT */
@font-face {
font-family: 'Inter';
font-display: optional; /* Không show fallback nếu font load chậm */
}
Astro Config Tối ưu
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
import compress from 'astro-compress';
export default defineConfig({
site: 'https://blogdev.work',
output: 'static',
build: {
inlineStylesheets: 'auto', // Inline small CSS
},
compressHTML: true,
vite: {
build: {
cssMinify: 'lightningcss',
rollupOptions: {
output: {
manualChunks: {
// Split vendor chunks
},
},
},
},
},
integrations: [
sitemap(),
compress({
CSS: true,
HTML: true,
Image: false, // Use Astro Image instead
JavaScript: true,
SVG: true,
}),
],
});
Monitoring & Testing
Lighthouse CI
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm run build
- name: Run Lighthouse
uses: treosh/lighthouse-ci-action@v10
with:
urls: |
https://blogdev.work
https://blogdev.work/posts/sample-post
budgetPath: ./lighthouse-budget.json
Budget File
// lighthouse-budget.json
[
{
"path": "/*",
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "first-input-delay", "budget": 100 },
{ "metric": "cumulative-layout-shift", "budget": 0.1 }
],
"resourceSizes": [
{ "resourceType": "script", "budget": 50 },
{ "resourceType": "total", "budget": 300 }
]
}
]
Real User Monitoring
<script>
// web-vitals library
import { onCLS, onFID, onLCP } from 'web-vitals';
function sendToAnalytics(metric) {
// Send to your analytics
console.log(metric.name, metric.value);
}
onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
</script>
Checklist Final
LCP
- Preload hero image
- Use WebP format
- Inline critical CSS
- Optimize fonts
- Use CDN
FID
- Minimal JavaScript
- Use client:visible/idle
- Partytown for third-party
- Code splitting
CLS
- All images have dimensions
- Use aspect-ratio
- Reserve space for dynamic content
- font-display: swap
Kết luận
Với Astro, đạt điểm Lighthouse 100 không khó nếu follow đúng best practices:
- Leverage Astro’s strengths: Zero JS by default
- Optimize images properly: Use Astro Image
- Load JavaScript smartly: client:visible, client:idle
- Monitor continuously: Lighthouse CI, web-vitals
Astro + đúng practices = Core Web Vitals perfect 🎯
Performance không chỉ là về SEO - nó là về user experience. Một site nhanh = users happy = conversion cao hơn.
Thử Thách Kiến Thức Lịch Sử?
Khám phá hàng trăm câu hỏi trắc nghiệm lịch sử thú vị tại HistoQuiz. Vừa học vừa chơi, nâng cao kiến thức ngay hôm nay!