Astro Presentation

Published on
Last updated on
Technical
Astro

Astro Presentation Proposal

Background

Last year, I began using marp to create slides for my presentations. The advantage of using marp is that I can write markdown and generate slides with it.

I also came across typst with its packages such as touying which can also be used to create presentations. I used it for a couple of slides - it is an awesome tool.

Both markdown and typst are in text format and can be stored in git with version control, so we can track the changes of slides more easily than with PowerPoint files.

Though both of them are not as flexible as PowerPoint and don’t support multimedia like audio and video, but we can use html <iframe> tag in the marp markdown to support audio and video (marp support html tags). they are sufficient for me to create slides for most scenarios. So I also want to integrate them into my blog, then I can share my slides with others or present them online.

Astro Integration with Marp

Astro is the framework I used to build my website. It leverages markdown or MDX files to create content, which is perfect for me.

But Astro doesn’t have a marp plugin itself, so we need to develop it ourselves.

I also created a feature request Integrate Marp with Astro for the marp team.

Updates

Today (2025/4/30), I initially implemented the astro-marp integration - not perfect but usable. My approach is to use marp-cli to convert the slide into HTML and save to dist/slide.

The full code:

---
import { type CollectionEntry, getCollection } from "astro:content";
import createSlug from "../../lib/createSlug";
import type { SlideSchema } from "src/content/config";
import type {
marpCli,
CLIError as CLIErrorType,
CLIErrorCode as CLIErrorCodeType,
} from "@marp-team/marp-cli";
import path from "path";
import { readFileSync } from "fs";
export async function getStaticPaths() {
const slideEntries = await getCollection("slide");
return slideEntries.map((slideEntry) => ({
params: { slug: createSlug(slideEntry.data.title, slideEntry.slug) },
props: { slideEntry },
}));
}
interface Props {
slideEntry: CollectionEntry<"slide">;
}
const { slideEntry } = Astro.props;
const slide: SlideSchema = slideEntry.data;
const { marpCli } = await import("@marp-team/marp-cli");
const marpGeneratedFile = path.join(
"dist/slide",
createSlug(slide.title, slideEntry.slug),
"index.html",
);
if (slideEntry.filePath) {
try {
await marpCli([
"--allow-local-files",
"--html true",
"--theme",
"./src/styles/marp/" + slide.theme + ".scss",
slideEntry.filePath,
"-o",
marpGeneratedFile,
]);
} catch (e) {
console.error(e);
throw e;
}
}
const marpGeneratedFileContent = readFileSync(marpGeneratedFile, "utf-8");
---
<html lang="en" set:html={marpGeneratedFileContent} />

In the meanwhile, I enabled Cloudflare R2 to host the images. Although Astro already processes the slide images locally, I don’t know how to replace the post-processed images after marp-cli execution, so I instead use online images.

  • Improvement
    • Can’t live preview the slide - any updates should be built first to see the result
    • Astro has already processed the images the slide referenced and saved to dist/_astro, but I don’t find a proper way to get the post-processed images and replace the href link in the post-marp HTML with these image files
    • Don’t have an approach to utilize the plugin rehypeMermaid which can directly process mermaid graphs
  • Plan
    • Maybe create a rehypeMarp plugin to process the marp slide??

Reference

Astro Integration with Typst

I found a new package astro-typst. It is an Astro Integration that lets you render Typst within Astro. It is in active development, so I will try this way to build the presentation, then I can write the presentation in typst document. According to the author of astro-typst, there are still some obstacles that should be resolved to make it possible.

Update: On 2025-05-08, I added this integration to the website - it worked. Added a sidebar book where all typst documents are located.

Also after adding typst integration, the Cloudflare Pages build will exhaust resources, so fallback to using GitHub Actions to deploy the website to Cloudflare Pages.

Progress

As of 2025-10-16, I have successfully implemented this plugin and integrated it into this website, marking it as production ready.

The next plan is to migrate from using marp-cli to marp-core for better governance.

Back to Blog