Skip to content

Astro Container API (experimental)

Added in: astro@4.9.0

The Container API allows you to render Astro components in isolation.

This experimental server-side API unlocks a variety of potential future uses, but is currently scoped to allow testing of .astro component output in vite environments such as vitest.

It also allows you to manually load rendering scripts for creating containers in pages rendered on demand or other “shell” environments outside of vite (e.g. inside a PHP or Elixir application).

This API allows you to create a new container, and render an Astro component returning a string or a Response.

This API is experimental and subject to breaking changes, even in minor or patch releases. Please consult the Astro CHANGELOG for changes as they occur. This page will always be updated with the most current information for the latest version of Astro.

Creates a new instance of the container.

import { experimental_AstroContainer } from "astro/container";
const container = await experimental_AstroContainer.create();

It accepts an object with the following options:

export type AstroContainerOptions = {
streaming?: boolean;
renderers?: AddServerRenderer[];
};
export type AddServerRenderer =
| {
renderer: NamedSSRLoadedRendererValue;
name: never;
}
| {
renderer: SSRLoadedRendererValue;
name: string;
};

Type: boolean

Enables rendering components using HTML streaming.

Type: AddServerRenderer[]

A list of loaded client renderers required by the component. Use this if your .astro component renders any UI framework components or MDX using an official Astro integration (e.g. React, Vue, etc.).

Renderers can be added through the Container API automatically for static applications, or cases where the container isn’t called at runtime (e.g. testing with vitest).

For on-demand rendered applications, or cases where the container is called at runtime or inside other “shells” (e.g. PHP, Ruby, Java, etc.), renderers must be manually imported.

Adding a renderer through the Container API

Section titled Adding a renderer through the Container API

For each official Astro integration, import and use the getContainerRenderer() helper function to expose its client and server rendering scripts. These are available for @astrojs/react, @astrojs/preact, @astrojs/solid-js, @astrojs/svelte, @astrojs/vue, @astrojs/lit, and @astrojs/mdx.

For renderer packages outside the @astrojs npm org, look in their documentation for getContainerRenderer() or a similar function provided.

When using vite (vitest, Astro integrations, etc.), the renderers are loaded with the function loadRenderers() from the virtual module astro:container. Outside vite, you’ll have to load the renderers manually.

The following example provides the necessary object to render an Astro component that renders a React component and a Svelte component:

import { getContainerRenderer as reactContainerRenderer } from "@astrojs/react";
import { getContainerRenderer as svelteContainerRenderer } from "@astrojs/svelte";
import { loadRenderers } from "astro:container";
const renderers = await loadRenderers([reactContainerRenderer(), svelteContainerRenderer()]);
const container = await experimental_AstroContainer.create({
renderers
})
const result = await container.renderToString(ReactWrapper);

When the container is called at runtime, or inside other “shells”, the astro:container virtual module’s helper functions are not available. Import the necessary server renderer manually and store it inside the container using experimental_Astro.Container.addServerRenderer.

The following example manually imports the server renderers for React and Vue components, and MDX for .mdx pages.

import reactRenderer from "@astrojs/react/server.js";
import vueRenderer from "@astrojs/vue/server.js";
import mdxRenderer from "astro/jsx/server.js";
const container = await experimental_AstroContainer.create();
container.addServerRenderer({renderer: reactRenderer});
container.addServerRenderer({renderer: vueRenderer});
container.addServerRenderer({renderer: mdxRenderer});

This function renders a specified component inside a container. It takes an Astro component as an argument and it returns a string that represents the HTML/content rendered by the Astro component.

import { experimental_AstroContainer } from "astro/container";
import Card from "../src/components/Card.astro";
const container = await experimental_AstroContainer.create();
const result = await container.renderToString(Card);

Under the hood, this function calls renderToResponse and calls Response.text().

It also accepts an object as a second argument that can contain a number of options.

It renders a component, and it returns a Response object.

import { experimental_AstroContainer } from "astro/container";
import Card from "../src/components/Card.astro";
const container = await experimental_AstroContainer.create();
const result = await container.renderToResponse(Card);

It also accepts an object as a second argument that can contain a number of options.

Both renderToResponse and renderToString accept an object as their second argument:

export type ContainerRenderOptions = {
slots?: Record<string, any>;
props?: Record<string, unknown>;
request?: Request;
params?: Record<string, string | undefined>;
locals?: App.Locals;
routeType?: "page" | "endpoint";
};

These optional values can be passed to the rendering function in order to provide additional information necessary for an Astro component to properly render.

Type: Record<string, any>;

An option to pass content to be rendered with <slots>.

If your Astro component renders one default slot, pass an object with default as the key:

import Card from "../src/components/Card.astro";
const result = await container.renderToString(Card, {
slots: { default: "Some value"}
});

If your component renders named slots, use the slot names as the object keys:

---
---
<div>
<slot name="header" />
<slot name="footer" />
</div>
import Card from "../src/components/Card.astro";
const result = await container.renderToString(Card, {
slots: { "header": "Header content", "footer": "Footer" }
});

You can also render components in cascade:

---
---
<div>
<slot name="header" />
<slot name="footer" />
</div>
import Card from "../src/components/Card.astro";
import CardHeader from "../src/components/CardHeader.astro";
import CardFooter from "../src/components/CardFooter.astro";
const result = await container.renderToString(Card, {
slots: {
"header": await container.renderToString(CardHeader),
"footer": await container.renderToString(CardFooter),
}
});

Type: Record<string, unknown>

An option to pass properties for Astro components.

import Card from "../src/components/Card.astro";
const result = await container.renderToString(Card, {
props: { name: "Hello, world!" },
});
---
// For TypeScript support
interface Props {
name: string;
};
const { name } = Astro.props;
---
<div>
{name}
</div>

Type: Request

An option to pass a Request with information about the path/URL the component will render.

Use this option when your component needs to read information like Astro.url or Astro.request.

You can also inject possible headers or cookies.

import Card from "../src/components/Card.astro";
const result = await container.renderToString(Card, {
request: new Request("https://example.com/blog", {
headers: {
"X-some-secret-header": "test-value"
}
})
});

Type: Record<string, string | undefined>;

An object to pass information about the path parameter to an Astro component responsible for generating dynamic routes.

Use this option when your component needs a value for Astro.params in order to generate a single route dynamically.

---
const { locale, slug } = Astro.params;
---
<div></div>
import LocaleSlug from "../src/components/[locale]/[slug].astro";
const result = await container.renderToString(LocaleSlug, {
params: {
locale: "en",
slug: "getting-started"
}
});

Type: App.Locals

An option to pass information from Astro.locals for rendering your component.

Use this option to when your component needs information stored during the lifecycle of a request in order to render, such as logged in status.

---
const { checkAuth } = Astro.locals;
const isAuthenticated = checkAuth();
---
{isAuthenticated ? <span>You're in</span> : <span>You're out</span> }
import Card from "../src/components/Card.astro";
test("User is in", async () => {
const result = await container.renderToString(Card, {
locals: {
checkAuth() { return true }
}
});
// assert result contains "You're in"
})
test("User is out", async () => {
const result = await container.renderToString(Card, {
locals: {
checkAuth() { return false }
}
});
// assert result contains "You're out"
})

Type: "page" | "endpoint"

An option available when using renderToResponse to specify that you are rendering an endpoint:

container.renderToString(Endpoint, { routeType: "endpoint" });
import * as Endpoint from "../src/pages/api/endpoint.js";
const response = await container.renderToResponse(Endpoint, {
routeType: "endpoint"
});
const json = await response.json();

To test your endpoint on methods such as POST, PATCH, etc., use the request option to call the correct function:

export function GET() {}
// need to test this
export function POST() {}
import * as Endpoint from "../src/pages/api/endpoint.js";
const response = await container.renderToResponse(Endpoint, {
routeType: "endpoint",
request: new Request("https://example.com", {
method: "POST" //
})
});
const json = await response.json();