🚀 Using Markdoc with Astro #
Markdown is a popular choice for authoring on content-rich websites. MDX offers extensions, providing access to React components, for example, within the content source. In this post, we see why you might reach for Astro Markdoc, instead of Markdown or MDX for your content site. We see how you can integrate Markdoc into an Astro documentation or content site, making full use of your favourite Astro features, such as underlying framework flexibility and Content Collections with front matter validation.
Why use Markdoc? #
Markdoc, created at Stripe, is a declarative alternative, to MDX. Like MDX, it offers power and flexibility for customizing your content. Where Markdoc excels is in defining component attributes in a schema. This simplifies the tooling and makes writing content, rather than writing code, the focus. As well as reducing template complexity, Markdoc brings the benefits of validation and machine-readable markup.
What does Astro bring to the Party? #
The Astro Markdoc integration abstracts away parts of the Markdoc configuration, letting you focus on business logic. Astro also offers flexibility. We will see a code block syntax highlighting example, adding sample code to a documentation site. Initially, we let Astro and Markdoc handle creating beautiful code samples for the site. Then, we see how we can take more control, adding our own Astro component to render the coding blocks using an alternative code highlighting package.
You can even take this a step further and use Astro components as gateways to your own existing code block components written in Preact, React, Svelte or Vue. Although we focus on a fenced code block example here, you can apply the techniques to custom-render other nodes. A further Markdoc feature is custom tags, though we will save them for another time!
🧱 What are we Building? #
We make use of Astro Content Collections, allowing Markdoc content files to drive the content of the site. Content Collections, generate a list of site content for our home page. Links from that list will take you to the actual content pages, again generated using Content Collections. We create such a page for each Markdoc file in the project content directory, using our own template.
If you are already familiar with Content Collections, there is little to learn there, as the API works with Markdoc near identically to how it works with Markdown source. However, you will hopefully be impressed by how little code we need to go from rendering the code blocks using the built-in Shiki theme to our own custom Astro component. Here we will use the starry-night package, which offers a number of accessible theme alternatives and automatic dark/light mode switching.
⚙️ Astro Markdoc Configuration #
We won’t build the site from start-to-finish here, though there is a link to the full code repo further down. Instead, we assume you have an Astro project already set up and are comfortable customizing it. If you are new to Astro, you might find the Getting Started with Astro post provides a gentle introduction.
Markdoc Astro Integration #
To start, run this command from your project folder to fire up the Astro Markdoc integration:
This will add the necessary packages to your project, and also update the config in astro.config.mjs
. The updated Astro config file should look something like the example below. Remember to create
a work-in-progress git feature branch before starting out here!
astro.config.mjs
— click to expand code.
Markdoc Config #
Because we want to tap into code syntax highlighting in our generated content, we will add a markdoc.config.mjs
file to the project root directory with the following content:
I opted for Shiki syntax highlighting here, and Astro Markdoc also supports Prism . For the theme, I chose One Dark Pro. If that is not your cup of tea, see a full list of Shiki themes in the Shiki GitHub repo. We will switch to starry-night highlighting later, so don’t invest too much time in finding the perfect theme yet!
🖋️ First Markdoc Post #
Next, create a collection folder in your project. This has to be within the src/content
folder. I went for a code
collection, and added content for this
collection to a new src/content/code
directory. Note that Markdoc files
normally have a .mdoc
extension. Paste this markup into your new content
source file (src/content/code/hello-world.mdoc
, for example) to
get started:
You will see this is not too different to Markdown, so I won’t go into too much detail here. If you are not familiar with Markdown, then the Markdoc docs give you a good primer on the syntax . That said, some points to note are:
- you can write front matter in GraphQL, JSON or YAML (I opted for JSON here);
-
Markdoc variables let you place front matter metadata in the markup (example in line
9
where we use the front matter title field); and -
you can add a custom
tag
, which gives you more flexibility, using your Astro components in the markup, though we stick to the basics here.
Astro Content Collection Schema #
Content Collection schema, is a way to define which fields we want in the front matter for each
markup file. The schema is optional, though useful because Astro can let you know if you forget to
add a field that is required by your schema. For that reason, I added a schema file at src/content/config.ts
:
src/content/config.ts
— click to expand code.
This also generates TypeScript types under the hood, for your content front matter metadata. Add another Markdoc file to your collection folder, just so you can try listing all content in the next section.
📝 Getting a List of Markdoc Content #
Typically, each Markdoc file will be a separate page on your generated site, and you will need a list of all content pages, often on the home page. We create that list here.
To get all Markdoc files in the collection:
-
import
getCollection
fromastro:content
; -
call
getCollection
, providing your collection name as an argument; and - render the array generated.
- name the template
src/pages/[...slug].astro
; and -
call
getStaticPaths
in the template file, returningparams.slug
to let Astro know how to map a Markdoc file to a pageslug
. -
starry-night gives you a lot of control over adding accessible themes. In line
9
, we imported@wooorm/starry-night/style/both
which provides the CSS needed to show a light or dark theme depending on the user’s browser preference. Other options include light, dark, and then, more specialized styles such as colorblind-light and dark, high-contrast-light and dark and tritanopia . -
The package supports highlighting many common languages using the
common
list, in line2
. For less common languages, though, you will need to extend the list, as we do for Elixir, in line10
. See a list of all supported languages and which languages starry-night includes in the common language list for more. -
Calling
starryNight.highlight
generates Hypertext AST (HAST), and to convert this to HTML we usehast-util-to-html
. - why choose Markdoc over Markdown or MDX;
- how you customize Markdoc nodes with custom Astro components; and
- how to set up starry-night GitHub-like syntax highlighting.
- creating custom Astro components for rendering headings or other nodes;
- adding Markdoc tags for increased flexibility; and
- coding up your custom components in your favourite framework, using the Astro component as a gateway. Pass through the necessary props to the framework component.
We follow these steps in src/pages/index.astro
, below:
Go to http://localhost:4321
in your browser to check the list of
pages gets created, as expected.
🍪 Creating an Astro Markdoc Template #
Astro template files generate site pages from a content collection. The path of the page is determined by the name you give your template and some metadata from the source.
Here, we want the generated page path to come from the slug of the Markdoc source (its path within the Content Collection). To make this work, we:
You can see this in action in src/pages/[...slug].astro
,
below:
Another interesting part is how we access Markdoc source front matter in the template. The
bottom part of the file renders the content from the Markdoc source. You can see in line 22
, we have a frontmatter
prop on the Content
element. This is important for making Markdoc variables
within
the source work. Remember, we had a variable in the source, which referenced the front matter title.
We pass data
into the frontmatter
prop for that reason.
Moving up the file, you can see data
comes from entry
(line 15
), which, itself, comes from Astro.props
(line 13
). We create the props
object in the getStaticPaths
function call (line 9
). You will see a similar pattern for sourcing title
and description
, passed to the layout template in line 20
.
Open up one of your content pages in a browser. Hopefully you see the code blocks with highlighting provided by the One Dark Theme, with Shiki. In the next section, we see how you can use starry-night to highlight the code blocks. We will generate a similar look to GitHub and also, automatically switch between light and dark syntax highlighting; based on the user theme dark/light preference.
✨ starry‑night Syntax Highlighting #
Next, we will see how you do not have to accept defaults for rendering nodes, and can instead use your own Astro components. You can see a full list of nodes which you can apply this technique to, in the Markdoc docs .
We will override the fence
rendering with our own component by
updating markdoc.config.mjs
:
Notice the attributes
for fence
are content
and language
(there
is also process
, but we will not use it, here). If you are
modifying other nodes, check the previous link to see the available attributes.
🧩 Astro Markdoc Fence Component #
We just referenced a Fence
component in the Markdoc config file,
but we have not yet created that component! Let’s do that now. Create a src/components
directory in your project, then make a Fence.astro
file in there,
with this content:
See starry-night docs for more details on how it works. Some interesting points to note in the starry-night setup here are:
To add starry-night to your project, run:
Our Astro Component #
Notice the Astro Fence
component takes content
and language
props (lines 15
– 18
) and renders these itself (pre
tag in lines 25
– 27
). If you preferred to work in Preact, React, Svelte or Vue, for example, you would replace
that pre
tag with your own component for that framework, passing
through the props. This is a feature I like most about Markdoc: it is not tied to React, and gives
you flexibility to add components from other frameworks.
Try toggling light and dark theme on your browser to check the starry-night syntax
highlighting gets updated to match the preference. Emulate prefers-color-scheme
in Chromium and Firefox .
🗳 Poll #
🙌🏽 Astro Markdoc: Wrapping Up #
In this post, we saw how you can create an Astro Markdoc site. In particular, we saw:
You can see the full code for this project in the Rodney Lab GitHub repo . There are quite a few extensions you might consider including:
I do hope you have found this post useful! Let me know how you use what you have learned here. Also, let me know about any possible improvements to the content above.
🙏🏽 Astro Markdoc: Feedback #
Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please consider supporting me through Buy me a Coffee.
Just dropped a new post on using Markdoc with 🚀 Astro.
— Rodney (@askRodney) November 8, 2023
We:
— take a look at Markdoc template variables;
— add code blocks with built-in Shiki support; and
— extend highlighting with our own Astro starry-night component.
Hope you find it useful!
https://t.co/EWXwA3Bqag
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on X, @[email protected] on Mastodon and also the #rodney Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as SEO. Also, subscribe to the newsletter to keep up-to-date with our latest projects.