Reference
@micacms/astro
The loader options, the _mica metadata, the helpers for pages, the rich-text functions, the map component, the live preview, and the shape of images and relations.
7 min read@micacms/astro connects the model to Astro's content layer. It reads the files during the build and needs no server, no token and no network.
micaCollections(site, options)
// src/content.config.ts
import { micaCollections } from "@micacms/astro";
import { site } from "./mica";
export const collections = micaCollections(site);
It gives one Astro collection for each collection and for each singleton of the model. A singleton is a collection with one entry, whose id is the singleton's name. The build also writes the model to <content root>/.mica/schema.json. Commit that file: the editor reads the model from it. In CI (when the environment variable CI is set), the build does not write the file. It fails when the committed file is not the current model.
| Option | Type | Default | Effect |
|---|---|---|---|
root | string | defineSite({ root }), else ./content | The content root. A relative path starts at the Astro project. Use an absolute path for content in a different checkout. |
publishModel | false | on | false does not write .mica/schema.json. Use it only for a project that nobody edits with Mica. |
debounceMs | number | 100 | In astro dev, the wait after a file change before Mica reloads. One save changes several files. |
strict | boolean | false | Stops the build at a relation to a missing entry or at a missing media file. Without it, the build shows a warning. A missing image stops the build in all cases. |
You can put ordinary Astro collections next to the Mica collections:
export const collections = { ...micaCollections(site), glossary };
For a reference set of the model, the loader also writes its index to <content root>/.mica/references/<name>.json. It writes it again in astro dev when the file changes. In CI, the build fails when the committed index is not the one of the file. See Reference data.
The data of an entry
entry.id is the ID, which is also the folder or file name in the repository. Each entry of a collection has data._mica. It holds two keys:
| Key | Meaning |
|---|---|
position | The place in the collection's order, the order that the editor shows: the defaultSort of the collection, else the order in _order.json when there is one, then the other IDs in the order of their characters (UTF-16 code units), the same on every computer. |
images | The images of the entry's rich text whose files are in the content, as { path, src }: the path as the text writes it, and the image that Astro made. A singleton has data._mica.images too. renderRichText reads them, so you rarely use them yourself. |
_mica has no id or collection. Use entry.id and the name of the collection. There are no dates in _mica, because in Git a date is commit data. Add a date() field when editors must see or set a date.
A localized field is an object with a value for each locale, such as { de: "Über uns", en: null }. localize gives the data in one locale.
Helpers for pages
| Helper | Gives |
|---|---|
inEditorOrder | A function for sort: it puts the entries in the order that the editor shows. (await getCollection("team")).sort(inEditorOrder) |
getSingleton(site, name) | The data of a singleton, typed by the model. It stops the build with a clear message when nobody saved the singleton yet. |
localize(site, name, data, locale) | The data of an entry or a singleton in one locale, such as Astro.currentLocale, typed by the model. Each localized field gives the value of that locale, or of the next locale in its chain: fallback, then defaultLocale. Without locale, the default locale. See Localization. |
mediaFile(site, path) | The file on disk for a media path, to read a GPX track or another file at build time. A .. in the path is refused. |
See Recipes for each one in use.
Media
| Field | Value in Astro |
|---|---|
media({ accept: "image" }) | An image (ImageMetadata), ready for <Image src={...} />. |
media({ accept: "image", alt: "required" }) or "optional" | { src, alt }, so <Image {...entry.data.cover} /> is enough. A missing optional alt text is "". |
media() with another accept | The path of the file from the content root, as a string, or { path, alt } with alt. Astro does not copy such a file. Read it with mediaFile, or copy it in an endpoint. |
multiple: true | A list of the values above. |
Relations
A relation stores the ID of the target entry, which is also its id in Astro. @micacms/astro/relations gives two functions for your pages.
import { related, relatedAll } from "@micacms/astro/relations";
const author = await related("team", article.data.author);
const people = await relatedAll("team", project.data.team);
related(collection, id)gives the entry, orundefinedwhen the field is empty or the entry does not exist.relatedAll(collection, ids)gives the entries in the order that the editor chose, and leaves out the entries that do not exist.
In astro dev, a missing entry shows a warning. Use them only in pages and components: they import astro:content. They also read the items of a reference set, because it is an Astro collection.
Rich text
A richText() field is Markdoc, in a .mdoc file beside the entry. The functions run during the build.
| Function | Gives |
|---|---|
await renderRichText(source, entry.data, options?) | An HTML string. An empty source gives "". It is async. |
await transformRichText(source, entry.data, options?) | Markdoc's render tree, to render with your own components. |
validateRichText(source, options?) | Markdoc's list of problems, for a strict build. renderRichText does not stop at a problem. |
The second argument is the data of the entry or the singleton. renderRichText finds its images in _mica.images, and Astro's image service makes them smaller: a srcset and sizes in 480, 800, 1200 and 1600 px as WebP, up to the width of the original. The site needs sharp, as for <Image>. An image with a /path that is not in the content, for example one in public/, keeps its path.
| Option | Effect |
|---|---|
config | A Markdoc configuration. Add your own tags there; a tag that the configuration does not declare gives no output. |
optimize | false writes the original files with their width and height. An object sets widths, sizes and format ("webp", "avif", "png" or "jpeg"). |
link | A function that gives each link its address, for example with the page's locale: /team/ becomes /de/team/. Editors then write one address for all languages. |
Markdoc refuses javascript: and data: links in its own nodes, but not in a tag or node that you add: check every URL attribute of your own tags, so that an editor cannot add a script link. Raw HTML in the source is not rendered. See Recipes for a custom tag.
Maps: @micacms/astro/components
LocationMap draws the value of a coordinates() field with MapLibre. It has no styles of its own: give it a size with class or style.
---
import { LocationMap } from "@micacms/astro/components";
---
<LocationMap place={venue.place} map="positron" label="Map: Town hall" class="block h-56 w-full" />
| Prop | Effect |
|---|---|
place | The value of the field: { lat, lng }, with zoom when the editor saved one. |
map | The field's map option, or another style. Default: "liberty". |
zoom | The zoom when the value stores none. Default: 14. |
label | The name of the map for a screen reader. |
linkText | The text of the link that stands in for the map. |
The website needs maplibre-gl in its dependencies. The map loads when it comes near the screen. Until then, and without JavaScript, the element holds a link to the place on OpenStreetMap. mapView(place, { map, zoom }) from @micacms/astro gives the same view as data, for a map that you draw yourself.
Live preview: @micacms/astro/preview
micaPreview() lets the local editor's preview find the texts of a page. A click on a text in the preview opens its field, and the preview scrolls to the field that the editor works in.
// astro.config.mjs
import { micaPreview } from "@micacms/astro/preview";
export default defineConfig({ integrations: [micaPreview()] });
Start the dev server with MICA_PREVIEW=1 astro dev. Only then does the integration mark the page. A build and a plain astro dev do not change. mica start --preview starts the dev server like this beside the local editor, on every system:
{
"scripts": {
"mica": "mica start --preview"
}
}
MICA_PREVIEW=1 astro dev sets the variable in a POSIX shell only. For a package script of the dev server alone that also runs on Windows, use cross-env MICA_PREVIEW=1 astro dev.
In each astro dev, also without MICA_PREVIEW, the integration tells the local editor the port that the dev server listens on. So the editor finds the dev server also when Astro takes another port, because the port of the config is busy.
- The loaders append invisible characters to the values of text, textarea and rich-text fields, and to the alt text of images. Values that look like an address or a phone number get none, because a template puts them in a link. In the preview, a template that compares a text value or makes an address from it can act differently.
- A value that the page shows formatted, such as a date, a number, an option, a map or an image without alt text, cannot carry the characters. The dev server reads each
.astrotemplate with Astro's own parser before Astro compiles it, and addsdata-mica-fieldsto each element, with the fields that its expressions read. The file on disk does not change. A value that another module reads and formats by itself is not found. The data thatlocalize(site, name, entry.data, locale)gives keeps the fields ofentry.data. - The parser is
@astrojs/compiler-rs, the one that the site's Astro installs. You install nothing for it. At the start, the dev server checks that the parser places the fields right. The parser of Astro 7.0 to 7.2 does not: then only the marked values can be clicked, and the dev server says so. Astro 7.3 or newer has a parser that works. renderRichTextandtransformRichTexttake the characters off a rich text. They mark the whole text with its field (data-mica-field) and each top-level block with its place (data-mica-block). A template that renders rich text in another way gets no marks for it.- A component gets its values as props. The element that uses it reads the fields, such as
<TagList tags={tags} />, and the root element of the component gets them too. - In
sections.map((section) => …)directly on a field, the elements name the row of the item, such assections.2.layout, and a click goes to that row. For this, the dev server gives the callback an index parameter when it has none. After.filter(),.slice()or another call, the index is not the row, and the elements name the whole list. - Each page gets a small script. It answers only an editor on the same computer. To allow another editor, set
MICA_PREVIEW_EDITORto its origins, separated by commas.
See Preview changes.
Types
import type { CollectionDocument, SingletonDocument } from "@micacms/astro";
import type { site } from "./mica";
type Project = CollectionDocument<typeof site, "projects">;
type Settings = SingletonDocument<typeof site, "settings">;
getCollection and getEntry also give these types, because the schema comes from the model.