Recipes

This is a collection of Astro Content use cases, which are real world examples extracted from this current website.

Create navigation sidebars for pages and headings sections

👈  Here is a walkthrough for creating those handy navigation helpers you can see on both sides of the current page.  👉

This will demonstrate how Astro Content think about declarative content modeling until its final usage, in your templates, where the contextual, imperative logic is done.

Define an entity schema for your pages

Entity will be named "docs" with entry name "page".
We will defined two front matter properties for the main.md file,
title and order:

From: 📄  /docs/content/docs/doc-section.schema.yaml
properties:
  main:
    properties:
      title:
        type: string
      order:
        type: number

    required:
      - title
      - order

    additionalProperties: false

required:
  - main

additionalProperties: false

Create page entries

For example for one named "install", with the previously defined front matter:

From: 📄  /docs/content/docs/install/main.md
---
title: Installation
order: 2
---

## Method 1: Add to Astro project with _CLI_

With `yarn`, `npm` or `pnpm`, run this in your **existing** Astro project:

```sh
# If you want a fresh start ——v
# pnpm create astro && cd ./my-astro-site

pnpm astro add astro-content
pnpm content setup

(Truncated…)

Will all that set, Astro Content will stream these typings for you in the background:

type Docs = {
    cli?: Page | undefined;
    conventions?: Page | undefined;
    development?: Page | undefined;
    ... 7 more ...;
    workInProgress?: Page | undefined;
}

interface Page {
  main: MarkdownFile & {
    frontmatter?: {
      title?: string;
      order?: number;
      [k: string]: unknown;
    };
  };
}

Create the navigation component

Before we import our sidebar widgets in our pages, let's create an Astro component for this task.
It will show its passed property, either pages or current page headings, as lists.

From: 📄  /docs/src/components/TreeNavigation.astro
---
import { type MarkdownHeading } from 'astro';
/* ·········································································· */
import { type Docs } from '/content';
import { Link } from 'astro-link';
import './TreeNavigation.scss';
/* —————————————————————————————————————————————————————————————————————————— */

interface Props {
  pages?: Docs;
  currentPage?: string;
  headings?: MarkdownHeading[];
}
const { pages, headings, currentPage } = Astro.props as Props;
---

<nav class="component-tree-navigation">
  <ul>
    {
      pages
        ? Object.entries(pages)
            ?.sort(([, prev], [, cur]) =>
              (prev?.main?.frontmatter?.order || 0) <
              (cur?.main?.frontmatter?.order || 1)
                ? -1
                : 1,
            )
            .map(([key, page]) => (
              <li>
                <Link
                  class:list={['unstyled', currentPage === key && 'current']}
                  href={`docs/${key}`}
                >
                  {page?.main?.frontmatter?.title}
                </Link>
              </li>
            ))
        : headings?.map((heading) => (
            <li>
              <Link class="unstyled" href={`#${heading.slug}`}>
                {heading.text}
              </Link>
            </li>
          ))
    }
  </ul>
</nav>

Import the component in an Astro page

We can now import our sidebar widgets like this:

From: 📄  /docs/src/pages/docs/[page].astro
---
import { get } from '/content';
/* ·········································································· */
import Layout from '../../layouts/Default.astro';
import DocPage from '../../modules/DocPage.astro';
/* —————————————————————————————————————————————————————————————————————————— */

export async function getStaticPaths() {
  const files = await get(Astro.glob('/content/docs/**/*.{md,mdx,yaml}'));
  return (
    files.docs &&
    Object.entries(files.docs).map(([key]) => ({ params: { page: key } }))
  );
}

/* —————————————————————————————————————————————————————————————————————————— */

const content = await get(Astro.glob('/content/**/*.{md,mdx,yaml}'), {
  // NOTE: Ignore this. Used for collecting + building Web GUI static data.
  editMode: import.meta.env.PROD,
});
---

<Layout>
  <DocPage docs={content.docs} />
</Layout>
From: 📄  /docs/src/modules/DocPage.astro
---
import { type Docs, type DocSectionEntryNames } from '/content';
/* ·········································································· */
import { Link } from 'astro-link';
import TreeNavigation from '../components/TreeNavigation.astro';
import PrevNextNavigation from '../components/PrevNextNavigation.astro';
import './DocPage.scss';
/* —————————————————————————————————————————————————————————————————————————— */

const { docs } = Astro.props as { docs: Docs };
const { page } = Astro.params as unknown as { page: DocSectionEntryNames };

/* ·········································································· */

const Main = docs?.[page]?.main?.Content;
---

<main class="module-doc-page">
  <div class="navigations">
    <div>
      <h2>Sections</h2>
      <TreeNavigation pages={docs} currentPage={page} />
    </div>

    <div class="this-page">
      <h2>On this page</h2>
      <TreeNavigation headings={docs?.[page]?.main?.getHeadings()} />
    </div>
  </div>

  <article class="article-body">
    {Main && <Main components={{ a: Link }} />}

    <PrevNextNavigation pages={docs} currentPage={page} />
  </article>
</main>