Astro 5.9

By
Emanuele Stoppa
Matt Kane

Astro 5.9 has got your site on lockdown, with experimental support for Content Security Policy, rendering Markdown in content loaders, and more!

🔒 Batten-down the hatches:

To upgrade an existing project, use the automated @astrojs/upgrade CLI tool. Alternatively, upgrade manually by running the upgrade command for your package manager:

# Recommended:
npx @astrojs/upgrade
# Manual:
npm install astro@latest
pnpm upgrade astro --latest
yarn upgrade astro --latest

Experimental Content Security Policy support

Cross-site scripting (XSS) attacks are some of the most common threats faced by websites. By default, web pages can load whichever scripts and styles they want, from wherever they want. The most powerful defense against XSS attacks is to limit this. A Content Security Policy (CSP) lets you do that, with tools to lock down the page to a list of trusted resources.

While these are powerful tools, they can be tricky to implement by hand: it’s much easier if your framework can do it for you. Astro 5.9 introduces experimental support for CSP out of the box, making it easier to secure your Astro projects. This is Astro’s most upvoted feature request so far, and we certainly took our time to implement it. We hope this is worth the wait!

We designed the feature to work in all Astro render modes (static pages, dynamic pages and single page applications), with maximum flexibility and type-safety in mind. You heard that right! You can ditch the unpleasant unsafe-inline workaround, use all the Astro features that you like, any adapter for any runtime, and add an extra layer of security to your site.

How it works

One of the reasons why users like Astro is because it works everywhere – static hosts, serverless hosts, Node.js, edge runtimes, and with many frontend libraries (React, Vue, Svelte, etc). Because of this, an Astro CSP solution must work everywhere with any library.

We started by considering two common approaches to supporting CSP:

  • The nonce header, which generates a random value for each request and injects it into the HTML.
  • Calculating hashes of all the resources shipped to the browser, ensuring that only the scripts and styles that you expect to run will be executed.

While the nonce header is straightforward to implement, it requires a server/edge function that is able to generate the random value of nonce, add the Content-Security-Policy header to the Response object, and rewrite the HTML to inject the nonce value for each <script> and <style> element. This solution wouldn’t work for websites that are served from static hosts e.g. GitHub pages, and it wouldn’t work for Single Page Applications (SPAs) where scripts and styles might be injected dynamically during the lifecycle of the application.

The generation of hashes is more complex to implement because it requires knowing the exact content of every script and stylesheet on the page. However, it can support more use cases than the nonce header, so our choice was clear even though another fork in the road was waiting for us.

The CSP standard allows hashes to be provided to the browser either via the Response header called Content-Security-Policy, or using a <meta> element called http-equiv='content-security-policy'. As you may have guessed, using the Response header wouldn’t work for Astro, because it would leave out static websites and SPAs. For this reason, we decided to use the <meta> element to provide the CSP to the browser.

The end result is that Astro’s CSP will generate the <meta> element for you, with all the hashes of the scripts and styles that will be used in the page, even the ones that will be loaded dynamically!

Usage

To try out Astro’s CSP in your own projects today, enable the new experimental flag:

astro.config.mjs
import { defineConfig } from "astro/config"
export default defineConfig({
experimental: {
csp: true
}
})

If you already use the Content-Security-Policy header in your website via middleware or other means, you can still use it, and the browser will use the stricter policy of the header and the <meta> element.

But we didn’t stop there! Astro gives you further control over that <meta> content via configuration. You can change the default algorithm, insert additional directives, and more:

astro.config.mjs
import { defineConfig } from "astro/config"
export default defineConfig({
experimental: {
csp: {
// change the default algorithm
algorithm: "`SHA-512",
// insert additional directives
directives: [
"default-src: 'self'",
"image-src: 'https://images.cdn.example.com'"
],
// add more information to the `style-src` directive
styleDirective: {
hashes: [
"sha384-somehash" // hash generated for some external style e.g. white label, etc.
],
// **Override** default resources
resources: ["self", "https://styles.cdn.example.com"]
},
// add more information to the `style-src` directive
scriptDirective: {
hashes: [
"sha384-somehash" // hash generated for some external script e.g. analytics, jQuery, etc.
],
// **Override** default resources
resources: ["self", "https://script.cdn.example.com"],
// Toggle the keyword `strict-dynamic`
scriptDynamic: true
}
}
}
})

For more details, see the experimental CSP documentation.

Rendering Markdown in content loaders

Astro content collections have always supported rendering Markdown files using the render() function and <Content /> component, and Astro 5 added support for any loader to render any HTML content. However if you wanted to render Markdown content in a content loader, you had to handle the Markdown parsing yourself. This could be confusing as it was inconsistent with how Markdown was rendered in other parts of your site, and didn’t use the same Markdown configuration.

Astro 5.9 adds a new helper function to the loader context - renderMarkdown - that allows you to render Markdown content directly within your loaders. It uses the same settings and plugins as the renderer used for Markdown files in Astro, including any Markdown settings configured in the Astro project.

The renderMarkdown function is available in the loader context, and returns an object with two properties: html and metadata. These match the rendered property of content entries in content collections, so they can be used to easily add support for render() in a loader.

import type { Loader } from 'astro/loaders';
import { loadFromCMS } from './cms';
export function myLoader(settings): Loader {
return {
name: 'my-loader',
async load({ renderMarkdown, store }) {
const entries = await loadFromCMS();
store.clear();
for (const entry of entries) {
// Assume each entry has a 'content' field with Markdown content
store.set(entry.id, {
id: entry.id,
data: entry,
rendered: await renderMarkdown(entry.content),
});
}
},
};
}

Now, even with your custom loader, you can access render() and <Content /> just as if your Markdown content were stored locally in your project:

---
import { getEntry, render } from 'astro:content';
const entry = await getEntry('my-collection', Astro.params.id);
const { Content } = await render(entry);
---
<Content />

For more details, see the content loaders documentation.

Disable default styles in experimental responsive images

When using experimental responsive images, Astro applies default styles to ensure the images resize correctly. In most cases this is what you want – and they are applied with low specificity so your own styles override them.

However in some cases you may want to disable these default styles entirely. This is particularly useful when using Tailwind 4, because it uses CSS cascade layers to apply styles, making it difficult to override Astro’s default styles.

Astro 5.8.1 added a new image.experimentalDefaultStyles boolean configuration option for applying these default styles. It defaults to true, providing the current responsive image behavior. But if you’d rather not fight with Tailwind 4, setting !important styles everywhere, you can simply disable this option in your Astro config:

export default {
image: {
experimentalDefaultStyles: false,
},
experimental: {
responsiveImages: true,
},
};

For more details, see the experimental responsive images documentation.

Allow adapters to suppress logs about feature support

Astro adapters can declare whether or not they support certain Astro features, and whether that support is stable or experimental.

During builds, Astro will log a warning or error if a site is using features that are not supported by the adapter. However, sometimes an adapter may need to log more specific messages to help users resolve the issue. Previously this could be confusing, as these custom logs were printed in addition to the Astro-generated ones and may appear to contradict them.

Astro 5.9 adds an option to allow adapters to suppress the logging for unsupported features. An adapter author can add suppress: "all" (to suppress both the default and custom message) or suppress: "default" (to only suppress Astro’s default message):

setAdapter({
name: 'my-astro-integration',
supportedAstroFeatures: {
staticOutput: "stable",
hybridOutput: "stable",
sharpImageService: {
support: "limited",
message: "The sharp image service isn't available in the deploy environment, but will be used by prerendered pages on build.",
suppress: "default",
},
}
})

For more details, see the Adapters API reference.

Community

The Astro core team is:

Ben Holmes , Caleb Jasik , Chris Swithinbank , Emanuele Stoppa , Erika , Florian Lefebvre , Fred Schott , Fuzzy , HiDeoo , Luiz Ferraz , Matt Kane , Matthew Phillips , Nate Moore , Reuben Tier , Sarah Rainsberger , and Yan Thomas .

Thanks to all the other contributors who helped make Astro 5.9 possible with code and docs additions and improvements, including:

☘, Adriel Martinez, Alexander Niebuhr, Ankur Oberoi, Ariel K, Armand Philippot, Arpan Patel, Ben Limmer, Bjorn Lu, Bugo, Cansin Acarer, Daniel Puscher, Elliot Dong, Hiromasa Fujimori, Hunter Bertoson, Igor Teplostanski, JiPai, Joe, Jonás Perusquía Morales, Junseong Park, Juraj Kapsz, kato takeshi, Kenzo Fachin, knj, liruifengv, Louis Escher, Nin3, Paul Valladares, Reuben Tier, Robin Bühler, Stephen Hendricks, sugardave, Thomas Bonnet, and vivek lokhande.

Astro 5.8

Astro 5.8 is a Nodally fresh update which bumps the minimum required version of Node.js.

Astro 5.7

Astro 5.7 has a basketload of treats, including stable Sessions and SVG components and a new Experimental Fonts API.