Frameworks

Astro

Read what you publish from an Astro site with @hollware/astro, at request time or at build time, and render it with your own Astro components.

Astro

@hollware/astro gives Astro's content layer two loaders, and two components that render what they load.

npm install @hollware/astro

Read at request time

A live collection reads at request time, so what you publish shows on the next request, with no rebuild. On Cloudflare, bind your Hollware Worker in the site's wrangler.jsonc:

"services": [{ "binding": "HOLLWARE", "service": "my-cms", "entrypoint": "Hollware" }]
// src/live.config.ts
import { env } from 'cloudflare:workers'
import { defineLiveCollection } from 'astro/content/config'
import { hollwareBinding, hollwareLiveLoader } from '@hollware/astro'

const blog = defineLiveCollection({
  loader: hollwareLiveLoader({
    site: 'my-site',
    collection: 'blog',
    locale: 'en',
    reader: () => hollwareBinding(env.HOLLWARE),
  }),
})
export const collections = { blog }

Make one collection per locale. An entry's id is its slug and its data the frontmatter, with the fields Hollware fills already filled.

Read at build time

For a static site, hollwareLoader in src/content.config.ts reads every published entry once, at build time, over HTTP with a read token (npx hollware token create --read):

// src/content.config.ts
import { defineCollection } from 'astro:content'
import { hollwareLoader } from '@hollware/astro'

const blog = defineCollection({
  loader: hollwareLoader({
    site: 'my-site',
    collection: 'blog',
    locale: 'en',
    url: 'https://cms.example.com',
    token: process.env.HOLLWARE_TOKEN ?? '',
  }),
})
export const collections = { blog }

Give defineCollection a schema to validate the frontmatter, as with any loader.

Render an entry

---
import HollwareContent from '@hollware/astro/HollwareContent.astro'
import HollwareImage from '@hollware/astro/HollwareImage.astro'
import { getLiveEntry } from 'astro:content'
import Callout from '../components/Callout.astro'

const { entry } = await getLiveEntry('blog', Astro.params.slug!)
if (!entry) return new Response('Not found', { status: 404 })
---

<h1>{entry.data.title}</h1>
<HollwareImage entry={entry} field="img" />
<HollwareContent entry={entry} components={{ callout: Callout }} />
  • HollwareContent.astro renders the entry from its document, with your own Astro components for the components of your entries: ::callout{type="tip"} gives Callout.astro the prop type and its content in the default <slot />. An element with no mapping renders as its plain tag. It goes through the same rules as the HTML Hollware makes, so nothing in an entry runs a script.
  • HollwareImage.astro renders the image a frontmatter field holds, with its alt, its size and the srcset of its copies. Any attribute you give wins.

You can also use Astro's own render(entry): its <Content /> is the HTML Hollware made, which needs no component of yours.

Lists and translations

getLiveCollection('blog', { tag, limit }) lists the entries, without their body, each with the images of its frontmatter for HollwareImage.astro. hollwareTranslations(options, group) gives the { locale, slug } of each entry of a translation group, to link to.

Previews

A live collection previews drafts. Add the middleware, which exchanges the admin's preview link for a cookie:

// src/middleware.ts
import { hollwarePreview } from '@hollware/astro'
export const onRequest = hollwarePreview()
---
import { refusePreview } from '@hollware/astro'
const preview = Astro.locals.hollwarePreview
const { entry, error } = await getLiveEntry('blog', { id: Astro.params.slug!, preview })
if (error) refusePreview(Astro.locals, error)
---

With a token, the entry is its latest revision, a draft included, and the page is never kept. A token that expired is an error, never the published version. A build-time collection has no previews: a static page is built once, for everyone.

A whole site

examples/astro-blog in the repository is an Astro blog on a Worker that reads the playground at request time, with its callout as a component of its own.