Skip to content

Under developmentMica is experimental and not ready for production. The API and the content format can still change, so read the release notes before you upgrade.Follow the releases.

Docs/Recipes

Build

Recipes

Images with alt text, relations, order, settings, maps, files, custom rich-text tags, checks in CI, a changed URL, and common questions.

8 min read

Each recipe names where its code comes from: the example site, which Mica's CI builds, a test that CI runs, the starter, or an Astro option.

Show an image with its alt text

Give the field alt. Astro then gets { src, alt }, and Astro's <Image> takes both.

heroImage: media({ label: "Hero image", accept: "image", alt: "required" }),
---
import { Image } from "astro:assets";
const { heroImage } = page.data;
---
{heroImage && <Image {...heroImage} widths={[800, 1200, 1800]} sizes="92vw" />}

From src/mica.ts and src/pages/[...id].astro of the example. With alt: "required", an editor cannot publish an image without alt text. Use "optional" for an image that is only decoration: an empty alt text is "".

Show related entries

A relation stores the ID of the other entry. @micacms/astro/relations gets the entries.

---
import { related, relatedAll } from "@micacms/astro/relations";
const writer = await related("team", article.data.author);
const members = await relatedAll("team", project.data.team);
---
{writer && <a href={`/team/${writer.id}/`}>{writer.data.name}</a>}

From src/pages/news/[id].astro and src/pages/projects/[id].astro of the example. To show a relation from the other side, for example the projects of one person, filter the other collection:

const projects = (await getCollection("projects")).filter((project) => project.data.team.includes(member.id));

For a relation to data that Mica does not edit, see Reference data.

Show images and tables in rich text

With toolbar: "full", or "table" and "image" in a list, editors add tables and images to the text. A table needs no code. An image needs the data of the entry, so that it gets its file and its address on the website. Give renderRichText the entry's data as the second argument. It is async:

<RichText text={body} from={page.data} />
---
import { renderRichText, type RichTextSource } from "@micacms/astro";
interface Props { text: string | null; from?: RichTextSource | null }
const { text, from } = Astro.props;
const html = await renderRichText(text, from);
---
<div set:html={html} />

Shortened from src/pages/[...id].astro and src/components/RichText.astro of the example. Astro's image service makes each image smaller: a srcset in 480, 800, 1200 and 1600 px as WebP, up to the width of the original. The site needs sharp, as for <Image>. { optimize: false } as the third argument writes the original. Style table and img in your rich-text styles.

Use the order of the editor

When a collection is sortable, editors put its entries in an order. defaultSort sets the order too. Astro does not sort by it, so sort on your page with inEditorOrder:

import { inEditorOrder } from "@micacms/astro";

export async function getTeam() {
  const items = await getCollection("team");
  return [...items].sort(inEditorOrder);
}

From src/lib/content.ts of the example.

Read the settings

A singleton is an Astro collection with one entry. getSingleton reads it with the type of the model:

import { getSingleton } from "@micacms/astro";
import { site } from "./mica";

const settings = await getSingleton(site, "site");

It stops the build with a clear message when the settings are not saved yet. The example reads each singleton in src/lib/content.ts.

Show a place on a map

A coordinates({ map: "liberty" }) field gives editors a map to click on. On the website, LocationMap draws the same place. It has no styles of its own: give it a size.

---
import { LocationMap } from "@micacms/astro/components";
---
<LocationMap
  place={venue.place}
  map="positron"
  label={`Map: ${venue.name}`}
  class="block h-56 w-full overflow-hidden"
/>

From src/pages/events/[id].astro of the example. Add maplibre-gl to the dependencies of the website. The map loads when it comes near the screen. Until then, and without JavaScript, the element holds a link to the place on OpenStreetMap. The zoom is the one that the editor saved. mapView() from @micacms/astro gives the same view for a map that you draw yourself.

Read a file at build time

An image reaches Astro as an image. Another file, such as a GPX track, reaches Astro as a path from the content root. mediaFile gives the file on disk:

import { readFile } from "node:fs/promises";
import { mediaFile } from "@micacms/astro";
import { site } from "./mica";

const track = await readFile(mediaFile(site, entry.data.track), "utf8");

The example copies the downloads of its projects into the built site in src/pages/files/[...path].ts.

Add your own rich-text tag

Declare the tag in a Markdoc configuration, and give it to renderRichText. A tag that the configuration does not declare gives no output.

import Markdoc from "@markdoc/markdoc";
import { renderRichText } from "@micacms/astro";

const config = {
  tags: {
    callout: {
      render: "aside",
      attributes: { type: { type: String, default: "note", matches: ["note", "warning"] } },
      transform(node: Markdoc.Node, config: Markdoc.Config) {
        const attributes = node.transformAttributes(config);
        return new Markdoc.Tag("aside", { class: `callout callout-${attributes.type}` }, node.transformChildren(config));
      },
    },
  },
};

const html = await renderRichText(entry.data.body, entry.data, { config });
{% callout type="warning" %}
Book a time **before** you visit.
{% /callout %}

The test packages/astro/test/richtext.test.ts renders this configuration. The editor cannot show a tag that it does not know, so it shows a rich-text field with such a tag read-only. Add the tag in the .mdoc file, and use it for content that changes seldom.

Check the content in CI

- run: npm ci
# Every entry as Mica and the website read it: fields, media, links and layout.
- run: npx --no mica check
# In CI, the build fails when content/.mica/schema.json is not the model in the code.
- run: npm run build

From .github/workflows/check.yml of the starter, which runs in each new site. mica check stops the job at an error. The build stops at a model change without its schema.json: the editor would show the old model. The build does the same for the index of a reference set.

Change the ID of an entry

The ID is part of the page's URL. When an editor selects Change ID, Mica offers to update the links in other entries (on by default), and it tells the editor that old links from outside need a redirect. Add the redirect in astro.config.mjs:

export default defineConfig({
  redirects: {
    "/news/old-name/": "/news/new-name/",
  },
});

redirects is an Astro option. On a static host, Astro writes a small page that sends the visitor on. Some hosts can also use a redirect file.

Questions

Can two editors work at the same time? Yes. All editors of a site see the saved changes. When two editors change the same entry, the second save shows both versions, and the editor chooses.

Where are the changes before Publish? They are files in the working tree that are not committed: in your checkout for the local editor, or in the clone on the data volume for a self-hosted Mica.

Does Mica need a database? No. Git holds the content and its history.

Can I use Mica without Astro? The content is JSON, Markdoc and media files, so other tools can read it. Mica gives a loader for Astro only.

How do I undo a publish? Revert the commit with Git. See Scope.

Why can I not rename a media file? An entry finds a file by its path, so a rename changes each entry that uses it. Upload the file again and change the entries. You can delete a file when no entry uses it.

Can I see a page before I publish it? Yes. Start the local editor with npx mica start --preview: it also starts astro dev, and the editor shows the page beside the form. A self-hosted Mica shows the same preview from a preview container of the website. See Preview changes.

Can the site hold thousands of photos? Yes. Keep them in Git LFS. See Large files with Git LFS.

Is the website multilingual? See Localization.