Astro 5.15: Protezione Skew, Nuove API per Adapter e Font Preload Granulare
Esplora Astro 5.15: protezione automatica skew per Netlify, nuove API per adapter, controllo granulare del preload dei font e miglioramenti alla developer experience.

Astro 5.15 introduce la protezione automatica contro il deployment skew per Netlify, nuove API per adapter che consentono di personalizzare headers e asset, controllo granulare del preload dei font e miglioramenti alla developer experience. Questa release migliora significativamente l'affidabilità e le performance dei siti Astro in produzione.
🎯 Novità Principali
Protezione Skew per Netlify
Sincronizzazione automatica tra client e server durante i deployment:
// ✅ Astro 5.15 - Automatic Netlify skew protection
// Nessuna configurazione necessaria!
// Astro include automaticamente il deployment ID
// in tutte le richieste di asset e API calls
// Per fetch personalizzate, accedi al deployment ID
const deployId = import.meta.env.DEPLOY_ID;
const response = await fetch("/api/endpoint", {
headers: {
"x-deploy-id": deployId,
},
});
// La protezione funziona su:
// ✓ View Transitions
// ✓ Server Islands
// ✓ Prefetch
// ✓ Astro Actions
Cos'è il deployment skew?
Il deployment skew si verifica quando gli utenti caricano asset client vecchi mentre il server esegue codice nuovo, causando:
- Bug sottili e difficili da tracciare
- Comportamenti inaspettati
- Problemi durante deployment rapidi
- Errori su connessioni lente
Come funziona la protezione:
// Astro include automaticamente il deployment ID
// GET /assets/main.js?deployId=abc123
// POST /api/action
// Headers: { 'x-deploy-id': 'abc123' }
// Netlify rileva e gestisce automaticamente
// i mismatch tra versioni client/server
Nuove API per Adapter
API potenti per personalizzare headers e asset query parameters:
// ✅ Astro 5.15 - New Adapter APIs
// src/adapters/example-adapter.ts
export default function exampleAdapter() {
return {
name: "example-adapter",
hooks: {
"astro:config:done": async ({ setAdapter }) => {
setAdapter({
name: "example-adapter",
serverEntrypoint: "example-adapter/ssr.js",
// Nuova opzione client
client: {
// Headers automatici per fetch interni
internalFetchHeaders: () => {
const deployId = process.env.DEPLOY_ID;
if (deployId) {
return {
"x-deploy-id": deployId,
"x-custom-header": "value",
};
}
return {};
},
// Query params automatici per asset
assetQueryParams: process.env.DEPLOY_ID
? new URLSearchParams({
deployId: process.env.DEPLOY_ID,
version: "1.0.0",
})
: undefined,
},
});
},
},
};
}
// Headers inclusi automaticamente in:
// - Astro Actions
// - View Transitions
// - Server Islands
// - Prefetch
Vercel Adapter Migliorato:
// ✅ Vercel adapter ora usa le nuove API
// astro.config.mjs
import vercel from "@astrojs/vercel";
export default defineConfig({
adapter: vercel(),
// Abilita skew protection
output: "server",
});
// In ambiente Vercel con VERCEL_SKEW_PROTECTION_ENABLED
// la protezione funziona su tutte le feature Astro!
// Prima (Astro 5.14):
// - Limitazioni su alcune feature
// - Bug su View Transitions
// - Problemi con Server Islands
// Ora (Astro 5.15):
// ✓ Feature parity con Netlify
// ✓ Protezione affidabile ovunque
// ✓ Nessuna configurazione manuale
Caso d'uso per adapter personalizzati:
// Custom caching adapter
export default function cachingAdapter() {
return {
name: "caching-adapter",
hooks: {
"astro:config:done": async ({ setAdapter }) => {
setAdapter({
name: "caching-adapter",
serverEntrypoint: "./ssr.js",
client: {
internalFetchHeaders: () => ({
"Cache-Control": "public, max-age=3600",
"CDN-Cache-Control": "public, max-age=86400",
"Cloudflare-CDN-Cache-Control": "max-age=14400",
}),
assetQueryParams: new URLSearchParams({
v: process.env.BUILD_ID || Date.now().toString(),
cdn: "enabled",
}),
},
});
},
},
};
}
// Risultato:
// /assets/main.js?v=abc123&cdn=enabled
// Fetch con headers di cache personalizzati
Font Preload Granulare
Controllo preciso sui font variants da precaricare:
<!-- ✅ Astro 5.15 - Granular font preload -->
---
import { Font } from 'astro:assets';
---
<!-- Preload solo varianti specifiche -->
<Font
cssVariable="--font-roboto"
preload={[
{ subset: 'latin', style: 'normal' },
{ weight: '400' },
]}
/>
<!-- Prima (Astro 5.14): -->
<!-- Tutto o niente - preload di tutte le varianti -->
<Font cssVariable="--font-roboto" preload />
<!-- Ora puoi essere granulare: -->
<Font
cssVariable="--font-inter"
preload={[
// Solo Latin normale
{ subset: 'latin', style: 'normal' },
// Solo peso 400 e 700
{ weight: '400' },
{ weight: '700' },
]}
/>
Filtri disponibili:
---
import { Font } from 'astro:assets';
---
<!-- Filtra per weight -->
<Font
cssVariable="--font-heading"
preload={[
{ weight: '700' },
{ weight: '900' },
]}
/>
<!-- Filtra per style -->
<Font
cssVariable="--font-body"
preload={[
{ style: 'normal' },
{ style: 'italic' },
]}
/>
<!-- Filtra per subset -->
<Font
cssVariable="--font-multi"
preload={[
{ subset: 'latin' },
{ subset: 'latin-ext' },
]}
/>
<!-- Combina più filtri -->
<Font
cssVariable="--font-brand"
preload={[
{ weight: '400', style: 'normal', subset: 'latin' },
{ weight: '700', style: 'italic', subset: 'latin' },
]}
/>
Variable fonts intelligenti:
<!-- ✅ Variable fonts matching intelligente -->
<!-- Font variable con range 100-900 -->
<Font
cssVariable="--font-variable"
preload={[
{ weight: '400' },
]}
/>
<!-- Astro include automaticamente il variable font
perché copre il range richiesto -->
<!-- Layout.astro -->
---
import { Font } from 'astro:assets';
---
<html>
<head>
<!-- Heading font: solo bold -->
<Font
cssVariable="--font-heading"
preload={[{ weight: '700', subset: 'latin' }]}
/>
<!-- Body font: regular + italic -->
<Font
cssVariable="--font-body"
preload={[
{ weight: '400', style: 'normal', subset: 'latin' },
{ weight: '400', style: 'italic', subset: 'latin' },
]}
/>
<!-- Code font: solo mono regular -->
<Font
cssVariable="--font-mono"
preload={[{ weight: '400', subset: 'latin' }]}
/>
</head>
<body>
<slot />
</body>
</html>
<!-- Benefici:
- Download ridotti del 60-70%
- First Contentful Paint migliorato
- Cumulative Layout Shift ridotto
- Banda salvata
-->
🔧 Developer Experience
Copy Button per Stack Traces
# ✅ Astro 5.15 - Copy stack traces
# Error overlay ora include un pulsante copy
# per condividere facilmente gli stack trace con:
# - LLM (ChatGPT, Claude, ecc.)
# - AI editors (Cursor, GitHub Copilot)
# - Team members
# - Issue trackers
# Esempio di stack trace copiato:
Error: Cannot read property 'title' of undefined
at src/pages/blog/[slug].astro:12:25
at async renderPage (file:///node_modules/astro/dist/...)
at async renderToStaticMarkup (file:///node_modules/astro/dist/...)
Workflow migliorato:
// 1. Errore durante lo sviluppo
// src/pages/post.astro
---
const post = await getEntry('blog', slug);
console.log(post.data.title); // ❌ post è undefined
---
// 2. Clicca il pulsante copy nell'error overlay
// 3. Incolla in ChatGPT/Claude:
// "Ho questo errore in Astro, come lo risolvo?"
// [stack trace copiato]
// 4. Ricevi una soluzione veloce:
// "Il problema è che getEntry può ritornare undefined.
// Aggiungi un controllo null:
if (!post) {
return Astro.redirect('/404');
}
"
Wrangler Configuration Scaffolding
# ✅ Astro 5.15 - Automatic wrangler.jsonc
# Prima (Astro 5.14):
npx astro add cloudflare
# Poi configurare manualmente wrangler.jsonc
# Ora (Astro 5.15):
npx astro add cloudflare
# wrangler.jsonc creato automaticamente!
# wrangler.jsonc generato:
{
"name": "my-astro-project",
"compatibility_date": "2024-11-28",
"pages_build_output_dir": "./dist"
}
Configurazione Cloudflare completa:
// wrangler.jsonc
{
"name": "my-site",
"compatibility_date": "2024-11-28",
"pages_build_output_dir": "./dist",
// Variabili d'ambiente
"vars": {
"API_URL": "https://api.example.com"
},
// KV namespaces
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "your-kv-id"
}
],
// D1 databases
"d1_databases": [
{
"binding": "DB",
"database_id": "your-db-id"
}
],
// R2 buckets
"r2_buckets": [
{
"binding": "MY_BUCKET",
"bucket_name": "my-bucket"
}
]
}
Node.js 18 Deprecation
// ✅ Vercel adapter depreca Node.js 18
// astro.config.mjs
import vercel from '@astrojs/vercel';
export default defineConfig({
adapter: vercel({
// Node.js 18 ora deprecated
// Usa Node.js 20 o 22 (LTS)
}),
});
// Warning se usi Node.js 18:
// ⚠️ Node.js 18 is deprecated.
// Please upgrade to Node.js 20 or later.
// package.json
{
"engines": {
"node": ">=20.0.0"
}
}
🚀 Performance Impact
Font Loading Optimization
// ✅ Confronto performance font preload
// Prima (preload tutto):
// - 6 font files scaricati (180KB)
// - FCP: 1.8s
// - LCP: 2.4s
<Font cssVariable="--font-inter" preload />
// Dopo (granular preload):
// - 2 font files scaricati (60KB)
// - FCP: 1.2s (-33%)
// - LCP: 1.6s (-33%)
<Font
cssVariable="--font-inter"
preload={[
{ weight: '400', subset: 'latin' },
{ weight: '700', subset: 'latin' },
]}
/>
// Risparmio:
// - 120KB di download in meno
// - 0.6s più veloce su 3G
// - Migliore Core Web Vitals
Skew Protection Performance
// ✅ Overhead della protezione skew
// Overhead per request:
// - Header size: ~50 bytes
// - Query param size: ~30 bytes
// - Processing time: <1ms
// Benefici:
// - Zero bug da version mismatch
// - Deployment sicuri
// - User experience consistente
// Esempio real-world:
// Sito con 10k page views/giorno
// Deployment ogni 2 ore
// Senza protezione:
// - 50-100 errori/giorno da skew
// - Bug reports da utenti
// - Debugging time: 2-3 ore
// Con protezione:
// - 0 errori da skew
// - Zero bug reports
// - Zero debugging time
// ✓ ROI immediato!
🎨 Pattern Pratici
Pattern 1: Multi-Font Setup
<!-- ✅ Setup multi-font ottimizzato -->
<!-- src/layouts/Layout.astro -->
---
import { Font } from 'astro:assets';
---
<html>
<head>
<!-- Heading: solo bold, solo latin -->
<Font
cssVariable="--font-heading"
preload={[
{ weight: '700', subset: 'latin' }
]}
/>
<!-- Body: regular + italic, latin + latin-ext -->
<Font
cssVariable="--font-body"
preload={[
{ weight: '400', style: 'normal', subset: 'latin' },
{ weight: '400', style: 'normal', subset: 'latin-ext' },
{ weight: '400', style: 'italic', subset: 'latin' },
]}
/>
<!-- Code: mono regular, solo latin -->
<Font
cssVariable="--font-mono"
preload={[
{ weight: '400', subset: 'latin' }
]}
/>
<style is:global>
:root {
--font-heading: 'Inter', sans-serif;
--font-body: 'Inter', sans-serif;
--font-mono: 'Fira Code', monospace;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading);
font-weight: 700;
}
body {
font-family: var(--font-body);
font-weight: 400;
}
code, pre {
font-family: var(--font-mono);
}
</style>
</head>
<body>
<slot />
</body>
</html>
Pattern 2: Custom Adapter con Protezione Skew
// ✅ Adapter personalizzato con skew protection
// src/adapter/custom-adapter.ts
import type { AstroAdapter } from "astro";
interface CustomAdapterOptions {
deploymentId?: string;
cacheControl?: string;
}
export default function customAdapter(
options: CustomAdapterOptions = {}
): AstroAdapter {
return {
name: "custom-adapter",
serverEntrypoint: "./server.js",
hooks: {
"astro:config:done": async ({ setAdapter, config }) => {
const deploymentId =
options.deploymentId ||
process.env.DEPLOYMENT_ID ||
process.env.VERCEL_DEPLOYMENT_ID ||
process.env.NETLIFY_DEPLOY_ID;
setAdapter({
name: "custom-adapter",
serverEntrypoint: "./server.js",
client: {
// Skew protection headers
internalFetchHeaders: () => {
const headers: Record<string, string> = {};
if (deploymentId) {
headers["x-deployment-id"] = deploymentId;
}
if (options.cacheControl) {
headers["Cache-Control"] = options.cacheControl;
}
return headers;
},
// Version query params
assetQueryParams: deploymentId
? new URLSearchParams({
v: deploymentId,
t: Date.now().toString(),
})
: undefined,
},
});
},
},
};
}
// Usage
// astro.config.mjs
import customAdapter from "./src/adapter/custom-adapter";
export default defineConfig({
adapter: customAdapter({
cacheControl: "public, max-age=31536000, immutable",
}),
});
Pattern 3: Font Loading Strategy
<!-- ✅ Strategia progressive font loading -->
<!-- src/layouts/BaseLayout.astro -->
---
import { Font } from 'astro:assets';
// Critical fonts (above the fold)
const criticalFonts = [
{
var: '--font-heading',
preload: [
{ weight: '700', subset: 'latin' }
]
},
{
var: '--font-body',
preload: [
{ weight: '400', style: 'normal', subset: 'latin' }
]
}
];
// Optional fonts (below the fold)
const optionalFonts = [
{
var: '--font-accent',
preload: [
{ weight: '600', subset: 'latin' }
]
}
];
---
<html>
<head>
<!-- Critical fonts: preload -->
{criticalFonts.map(font => (
<Font
cssVariable={font.var}
preload={font.preload}
/>
))}
<!-- Optional fonts: lazy load -->
{optionalFonts.map(font => (
<Font
cssVariable={font.var}
preload={false}
/>
))}
<style is:global>
/* System font fallback */
:root {
--font-system: -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, sans-serif;
}
body {
/* Use system font until custom font loads */
font-family: var(--font-system);
}
/* Apply custom font when loaded */
.fonts-loaded body {
font-family: var(--font-body);
}
</style>
<script>
// Detect quando i font sono caricati
if ('fonts' in document) {
Promise.all([
document.fonts.load('400 1em Inter'),
document.fonts.load('700 1em Inter'),
]).then(() => {
document.documentElement.classList.add('fonts-loaded');
});
}
</script>
</head>
<body>
<slot />
</body>
</html>
📊 Metriche di Miglioramento
Font Loading
| Scenario | Prima (5.14) | Dopo (5.15) | Miglioramento |
|---|---|---|---|
| Fonts scaricati | 6 files | 2 files | -67% |
| Download size | 180KB | 60KB | -67% |
| First Paint | 1.8s | 1.2s | -33% |
| Largest Paint | 2.4s | 1.6s | -33% |
Deployment Reliability
| Metrica | Senza Skew | Con Skew | Miglioramento |
|---|---|---|---|
| Errori/giorno | 50-100 | 0 | -100% |
| Bug reports | 10-15 | 0 | -100% |
| Debugging time | 2-3h | 0h | -100% |
| User complaints | 5-8 | 0 | -100% |
🎓 Best Practices
1. Usa Font Preload Granulare
<!-- ✅ Preload solo ciò che serve -->
<Font
cssVariable="--font-body"
preload={[
{ weight: '400', subset: 'latin' }
]}
/>
2. Abilita Skew Protection
// ✅ Deploy su Netlify/Vercel con protezione
// Automatico - nessuna configurazione!
3. Ottimizza per Core Web Vitals
<!-- ✅ Critical fonts first -->
<Font cssVariable="--font-critical" preload={...} />
<!-- ✅ Optional fonts lazy -->
<Font cssVariable="--font-optional" preload={false} />
4. Custom Adapter per Edge Cases
// ✅ Estendi con le nuove API
client: {
internalFetchHeaders: () => ({ ... }),
assetQueryParams: new URLSearchParams({ ... }),
}
🔗 Risorse Utili
💡 Conclusioni
Astro 5.15 porta miglioramenti significativi per produzione:
✅ Protezione Skew automatica per Netlify ✅ Nuove API Adapter per personalizzazione avanzata ✅ Font Preload Granulare per performance ottimali ✅ Developer Experience migliorata con copy button ✅ Vercel Adapter aggiornato con feature parity
Aggiorna oggi stesso:
npx @astrojs/upgrade
Quando usare queste feature:
- ✅ Skew Protection: sempre su Netlify/Vercel
- ✅ Granular Preload: siti con molti font
- ✅ Custom Adapter: hosting personalizzato
- ✅ Copy Button: debugging rapido