@nohead/astro is the TypeScript SDK’s adapter for Astro 5.9 or later: a content loader for content collections, and an image service that resizes Nohead’s images through their URLs. It reads the API through the SDK, which comes with it, and has releases of its own.
Content collections
noheadLoader loads collections into Astro’s content collections, so pages read them with getCollection, getEntry, reference() and render(). Each published record becomes an entry whose id is the record ID and whose data is its fields, when the site builds and when astro dev starts. On Astro 6 or later, the loader also gives each collection its schema and TypeScript type from the collection’s fields, so there is no schema to write:
getCollection("posts") is then typed from the posts collection’s fields: post.data.title is a string, post.data.date a Date, post.data.authors references to people.
- Rich text reads as HTML, for
<Fragment set:html={post.data.summary} />.bodynames one rich text field that moves out ofdataforrender(entry)to render as<Content />. WithrichText: "markdown", rich text reads as Markdown and that field goes through Astro’s own Markdown pipeline (your plugins, syntax highlighting, headings). - Assets named in
expandreplace their ID with the asset: itsimage_url,width,height,altand the rest, ready for<Image>throughimageMetadataand the image service. - Relations are references, for
getEntry, to the Astro collection named like the related one, so define a collection for every one that relations point to. If you load it under another name, say so:references: { people: "authors" }. - The record’s own times are in
datatoo:published_at(when it was published; publishing again moves it),created_atandupdated_at, for feeds and a sitemap’slastmod. A date readers see, such as a post’s, is better a date field of its own: Content modelling explains why. - Empty fields are left out of
data, so they are optional. - Dates without a time become
Dates at midnight UTC, as Astro makes front-matter dates. Format them in UTC,date.toLocaleDateString("en-GB", { timeZone: "UTC" }), or west of Greenwich they show as the day before. - Order: Astro keeps no order for a collection, so sort entries in the page:
posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()). - Published records are the ones that load, so a published-only key is enough.
filteradds conditions, such as{ featured: true }, andfilter: { published_at: undefined }loads every record, for a key that reads unpublished ones.
Schema and types
The loader reads the collection’s fields when Astro syncs (astro dev, astro build, astro sync), with the key’s schema:read scope, which a published-only key can have (without it, the sync fails and says so):
- Text and rich text fields are strings (with
richText: "document", rich text is the API’s document object), numbers are numbers, dates areDates, enums are their options, booleans areboolean(falsewhen unset) and JSON isunknown. A multiple field is an array. - A required field is required and the others optional. An expanded asset or relation is always optional, since one deleted or unpublished since is left out.
- A relation is a
reference()to its collection, and an expanded one is{ id, data }. An asset that isn’t expanded stays its ID.
From the schema file
Reading the fields from the API takes a key, so without oneastro check and astro sync fail before checking anything, in CI or in a fork’s pull request. With schemaFile, the loader reads them from the schema file that nohead schema pull writes (a path from the folder Astro runs in, usually the project’s root), and the types are the same:
astro:env, make it optional there (envField.string({ context: "server", access: "secret", optional: true })), or Astro stops before the loader runs. Commit the file, and pull it again when the fields change, then restart astro dev. npx nohead schema diff --exit-code fails while the file and the project differ, for a CI check.
Your own schema
Aschema given to defineCollection takes the loader’s place, as with any Astro loader. Give one to transform values, or on Astro 5, which doesn’t ask loaders for a schema: there, fields are as the API returns them, so dates and the record’s times are strings (z.coerce.date() makes them Dates, a date without a time at midnight UTC) and relations are record IDs, for reference().
Builds and changes
The loader reads the API when the site builds and whenastro dev starts, so pages show what was published then, including pages rendered on demand. To rebuild when published content changes, point a published-only webhook at your host’s deploy hook: Webhooks has an example.
Images
Name the image service in astro.config,image: { service: { entrypoint: "@nohead/astro/image-service" } }. Astro’s <Image>, <Picture> and getImage then write each size and format into a Nohead image’s URL: the build never downloads or resizes it. Your site’s own images go through Astro’s sharp service, resized at build time as Astro does without it.
imageMetadata gives an image asset (an expanded one, or the SDK’s Asset) with its original size, so width and height are optional and no srcset width is larger than the original. An image_url works too, with width and height. Images has the details.
Source
The adapter is open source, under the MIT license. Report issues and follow releases on GitHub:nohead-io/nohead-astro
Loading repository data...