Skip to main content

Overview

Astro is designed for performance by default with zero JavaScript shipped to the client unless you explicitly add it. This guide covers techniques to further optimize your site for maximum speed.

Image Optimization

Images are often the largest assets on a webpage. Astro provides built-in image optimization through the <Image> component.

Using the Image Component

src/components/Hero.astro
---
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
---

<Image 
  src={heroImage} 
  alt="Hero image" 
  width={1200} 
  height={600}
  loading="eager"
  format="webp"
/>

Benefits of Image Optimization

Automatic Resizing

Images are resized to specified dimensions, reducing file size.

Format Conversion

Convert to modern formats like WebP and AVIF automatically.

Lazy Loading

Images load only when they enter the viewport.

Responsive Images

Serve appropriately sized images based on device and screen size.

Remote Images

Optimize images from external sources:
---
import { Image } from 'astro:assets';
---

<Image
  src="https://example.com/photo.jpg"
  alt="Remote image"
  width={800}
  height={600}
  inferSize
/>

Content Collections with Images

src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const blog = defineCollection({
  loader: glob({ base: './src/content/blog', pattern: '**/*.md' }),
  schema: ({ image }) => z.object({
    title: z.string(),
    heroImage: image(),
  }),
});

export const collections = { blog };
src/layouts/BlogPost.astro
---
import { Image } from 'astro:assets';

const { heroImage, title } = Astro.props;
---

<article>
  <Image 
    src={heroImage} 
    alt={title}
    width={1020}
    height={510}
    format="webp"
  />
</article>

Prefetching

Prefetch pages before users navigate to them for instant page transitions.

Enabling Prefetch

1

Configure prefetch in astro.config.mjs

astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  prefetch: {
    prefetchAll: true,
    defaultStrategy: 'hover',
  },
});
2

Add data attributes to links

<nav>
  <a href="/about" data-astro-prefetch>About</a>
  <a href="/blog" data-astro-prefetch="hover">Blog</a>
  <a href="/contact" data-astro-prefetch="tap">Contact</a>
</nav>

Prefetch Strategies

Prefetch when the user hovers over a link (default):
<a href="/about" data-astro-prefetch="hover">About</a>
Best for: Desktop navigation where hover intent is clear.
Prefetch when the user taps/clicks a link:
<a href="/products" data-astro-prefetch="tap">Products</a>
Best for: Mobile devices or slow connections.
Prefetch when links enter the viewport:
<a href="/blog/post" data-astro-prefetch="viewport">Read More</a>
Best for: Lists of links where users are likely to click visible items.
Prefetch immediately when the page loads:
<a href="/checkout" data-astro-prefetch="load">Checkout</a>
Best for: Critical next steps in user flows.

Programmatic Prefetching

---
// Server-side
---

<script>
  import { prefetch } from 'astro:prefetch';

  // Prefetch on custom event
  document.querySelector('#important-link')
    .addEventListener('focus', () => {
      prefetch('/important-page');
    });

  // Prefetch multiple pages
  const pages = ['/about', '/contact', '/pricing'];
  pages.forEach(page => prefetch(page));
</script>

Code Splitting

Astro automatically code-splits your JavaScript to load only what’s needed.

Client Directives

Control when JavaScript loads for interactive components:
---
import Counter from '../components/Counter.jsx';
import Chat from '../components/Chat.svelte';
import Analytics from '../components/Analytics.vue';
---

<!-- Load immediately -->
<Counter client:load />

<!-- Load when component is visible -->
<Chat client:visible />

<!-- Load when page is idle -->
<Analytics client:idle />

<!-- Only hydrate on media query match -->
<MobileMenu client:media="(max-width: 768px)" />

Directive Priority

1

client:load

Highest priority. Load and hydrate immediately on page load. Use for: Critical interactive elements above the fold.
2

client:idle

Load once the page is done with initial load and the requestIdleCallback event has fired. Use for: Non-critical widgets and chat boxes.
3

client:visible

Load once the component enters the user’s viewport. Use for: Below-the-fold interactive content.
4

client:media

Load only when a media query matches. Use for: Mobile-only or desktop-only components.
5

client:only

Skip server-side rendering entirely. Hydrate only on client. Use for: Components that rely heavily on browser APIs.

Asset Optimization

CSS Optimization

Astro automatically:
  • Scopes CSS to prevent conflicts
  • Minifies CSS in production
  • Removes unused CSS
  • Bundles CSS efficiently
src/components/Card.astro
<div class="card">
  <slot />
</div>

<style>
  .card {
    /* This CSS is automatically scoped and optimized */
    border-radius: 0.5rem;
    padding: 1rem;
  }
</style>

Font Optimization

Optimize web font loading:
src/components/BaseHead.astro
<head>
  <!-- Preload critical fonts -->
  <link
    rel="preload"
    href="/fonts/inter-var.woff2"
    as="font"
    type="font/woff2"
    crossorigin
  />
</head>

<style is:global>
  @font-face {
    font-family: 'Inter';
    src: url('/fonts/inter-var.woff2') format('woff2');
    font-weight: 100 900;
    font-display: swap; /* Show fallback while loading */
  }
</style>

Script Optimization

Optimize third-party scripts:
<!-- Defer non-critical scripts -->
<script defer src="/analytics.js"></script>

<!-- Load scripts only when needed -->
<script>
  // Load Google Maps only when user clicks
  document.querySelector('#map-trigger').addEventListener('click', () => {
    const script = document.createElement('script');
    script.src = 'https://maps.googleapis.com/maps/api/js';
    document.head.appendChild(script);
  });
</script>

Build Optimization

Minimize Bundle Size

Audit your dependencies regularly:
npx depcheck
Remove packages you’re not using:
npm uninstall unused-package
Import heavy libraries only when needed:
---
// Don't import at the top if not always needed
// import { Chart } from 'heavy-chart-library';
---

<button id="load-chart">Load Chart</button>

<script>
  document.querySelector('#load-chart').addEventListener('click', async () => {
    // Import only when needed
    const { Chart } = await import('heavy-chart-library');
    new Chart(/* ... */);
  });
</script>
Configure Vite to optimize specific dependencies:
astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  vite: {
    optimizeDeps: {
      include: ['heavy-library'],
    },
  },
});

Caching Strategies

Static Asset Caching

Configure your hosting provider to cache static assets:
Cache-Control: public, max-age=31536000, immutable
For Netlify:
netlify.toml
[[headers]]
  for = "/_astro/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"
For Vercel:
vercel.json
{
  "headers": [
    {
      "source": "/_astro/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ]
}

Performance Monitoring

Lighthouse Scores

Run Lighthouse audits regularly:
npm install -g lighthouse
lighthouse https://your-site.com --view

Core Web Vitals

Monitor key metrics:

LCP

Largest Contentful PaintTarget: < 2.5sOptimize images and fonts.

FID

First Input DelayTarget: < 100msMinimize JavaScript.

CLS

Cumulative Layout ShiftTarget: < 0.1Reserve space for images.

Best Practices Checklist

1

Use the Image component

Always use <Image> for local and remote images to get automatic optimization.
2

Enable prefetching

Configure prefetch for smoother navigation between pages.
3

Choose appropriate client directives

Use client:idle or client:visible for non-critical components.
4

Optimize fonts

Preload critical fonts and use font-display: swap.
5

Lazy load below-the-fold content

Use loading="lazy" for images and client:visible for components.
6

Minimize third-party scripts

Defer or dynamically load analytics and other third-party code.
7

Monitor performance

Regularly check Lighthouse scores and Core Web Vitals.

Advanced Optimizations

Deploy to edge networks for faster response times:
astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';

export default defineConfig({
  output: 'server',
  adapter: cloudflare(),
});
Edge rendering reduces latency by serving content from locations close to users.
Only hydrate the interactive parts of your page:
---
// Most of the page is static HTML
---

<header>
  <h1>My Site</h1>
</header>

<main>
  <p>This content is static and fast.</p>
  
  <!-- Only this button needs JavaScript -->
  <InteractiveButton client:visible />
</main>
Implement offline support and advanced caching:
public/sw.js
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) => {
      return cache.addAll([
        '/',
        '/styles.css',
        '/script.js',
      ]);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

Build docs developers (and LLMs) love