These pages document @evanion/astro-widget 0.3.4, the version on npm.
@evanion/astro-widget
Render CMS-driven pages with zero-JS overhead using Astro.
A content management system (CMS) saves a page as a list of items. To render this page, you need to map each item’s type to a specific .astro component and pass it the corresponding props.
@evanion/astro-widget provides the Widgets.astro component, which automates this process during Astro’s build phase. Because the rendering happens entirely on the server, it sends no JavaScript to the browser, ensuring maximum performance and an instant first paint.
The Problem: The “Static Content” Bottleneck
In many Astro projects, developers manually place components on a page. However, when a page is driven by a CMS, you don’t know which components are needed until the data is fetched.
Manually writing a switch statement to render components based on a type string is repetitive and error-prone. Furthermore, since .astro components don’t have the same prop-inference capabilities as React components in a TypeScript environment, it’s easy to miss a required prop in the CMS data, leading to broken pages.
Solution: Data-Driven Component Mapping
@evanion/astro-widget solves this by using a registry. You define a map where the keys are your widget types (e.g., 'listing-header') and the values are the corresponding .astro components.
The Widgets.astro component then takes your item array and the registry, rendering each item in order through its mapped component.
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import Widgets from '@evanion/astro-widget/components/Widgets.astro';
import { registry } from '../examples/src/registry';
const items = [
{
id: 'header',
type: 'listing-header',
props: { title: 'Brass: Birmingham', players: '2-4' },
},
{ id: 'price', type: 'price-box', props: { price: '649 kr' } },
];
const container = await AstroContainer.create();
const html = await container.renderToString(Widgets, {
props: { items, registry },
});
html; // -> '<header><h1>Brass: Birmingham</h1><p>2-4 players</p></header><p class="price">649 kr</p>'By decoupling the content (the item array) from the rendering logic (the registry), you can change the entire layout of a page simply by updating a JSON file in your CMS.
The astro-widget Vocabulary
These terms are used throughout the documentation:
- Item: A single entry in the CMS list. It contains an
id, atype, and thepropsfor its component. It can also carrymeta(placement data) andchildren(nested items). - Widget: The
.astrocomponent that renders an item. - Registry: The map linking each
typeto its widget.defineWidgetsis used to build a type-safe registry. - Problem: A fault identified by
validateItems, such as a type that isn’t in the registry.
Note: The core logic for items, the registry, and validation is provided by @evanion/widget. astro-widget re-exports these tools so you can maintain a single source of truth for your content validation.
Core Capabilities
| You want to… | Use this tool | Taught on |
|---|---|---|
| Render a page from CMS items | Widgets.astro | Getting started |
| Get autocomplete for type names | defineWidgets | Getting started |
| Fail the build on broken content | validateItems | Check CMS items |
| Pass page-level data to widgets | The ctx prop | Layout |
| Wrap widgets in shared markup | chrome.item | Layout |
| Render nested widgets | Recursive Widgets usage | Layout |
When astro-widget is the Wrong Tool
- Dynamic Content: If a page needs to change after it has loaded in the browser, use
@evanion/react-widgetwithin an Astro island. - Strict Prop Checking: If you need the compiler to verify every prop of every item, use
@evanion/react-widget. Astro components do not support the same level of prop inference. - Static Pages: If your page structure never changes and is not driven by a CMS, simply write the components directly into your
.astropage.
Build the Baize Listing Page
You’ve seen how Widgets.astro turns a few items into a header and a price box. Getting started guides you through installing the package, creating your first widgets, and rendering a full listing page from a CMS JSON file.