Astro / SEO

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.

newspaper

BlogDev Team

17 tháng 12, 2024
Tối ưu Core Web Vitals với Astro: Hướng dẫn đạt điểm 100
Featured Image

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 images
  • format="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:

  1. Leverage Astro’s strengths: Zero JS by default
  2. Optimize images properly: Use Astro Image
  3. Load JavaScript smartly: client:visible, client:idle
  4. 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.

history_edu Góc học tập & giải trí

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!

Chơi Ngay arrow_forward