mkaz.blog

Using Nextra to Blog

I switched this site over to use Nextra framework built on Next.js, previously I used my custom static site generator Hastie.

Documentation is a bit thin for Nextra. So I would only recommend it if you are familiar with Next.js and React, and in the mood to learn and struggle a bit. It's not bad, but definitely not plug-and-play if you want to convert an existing site over, or do anything beyond the default themes.

This post covers the basics of getting up and running with your own custom theme in Nextra.

Custom Theme

I wanted the new site to be quite similar to the existing site from basic design, layout, and structure, so I ended up creating my own theme and not using one of the built-in themes. I wouldn't really call what I made a theme because it's not really plug-and-playable into someone else's site.

In next.config.js, add a pointer to your theme:

import nextra from "nextra";
 
const withNextra = nextra({
    theme: "./theme/index.jsx",
});
 
export default withNextra({
    reactStrictMode: true,
    cleanDistDir: true,
});

In ./theme/index.jsx export a Layout function. The following is an example of the most basic; the content of the markdown pages will come in to the children object.

import React from "react";
import Head from "next/head";
 
export default function Layout({ children, pageOpts }) {
    const { title } = pageOpts;
    return (
        <>
            <Head>
                <title>{title}</title>
            </Head>
 
            <main>
                <article>{children}</article>
            </main>
        </>
    );
}

In pages directory create _app.mdx file with the following, I added my stylesheet imports here, I don't use Tailwind, just standard CSS.

import "../styles/style.css";
 
export default function App({ Component, pageProps }) {
    return <Component {...pageProps} />
 
}

Also in pages directory create whatever markdown or MDX files you like. These will be rendered as pages on your site, following the directory structure. Use frontmatter to define parameters such as title, whatever you put will be defined in the pageOpts.pageMap.[page].frontmatter

So for most basic theme directory structure:

pages/
    _app.mdx
    about.md
    index.md
next.config.js
styles/
    style.css
theme/
    index.jsx

Run npm run dev to run locally. I use Vercel to host, which makes it straight-forward, create a project on Vercel and point it to your repo, configure as Next.js project.

I created a repo with this starting template: github.com/mkaz/nextra-starter. For the rest of the theme, it is up to you, it's just React and content. Add whatever react components you want to build your pages and layout the site.

RSS

One part lacking was RSS support, there is a gen-rss.js part of next.js, but this script is a bit limited and didn't really work for me. It doesn't support recursion through multiple directories, and only includes a description in the feed.

You can check out Ibas Majid article on RSS for another good example and use as a reference.

I actually ended up using a Python script from my Hastie project to walk through the files and generate an RSS file.

Table of Contents

The default themes in Nextra include some options for automatically adding a table of contents in the sidebar. Since I'm not using a default theme, it took me a couple of beats to figure out how they were getting the data. It turns out there is a headings object inside a page within pageOpts. Be sure to explore what's all in pageOpts lots of good stuff.

Here's the table of contents code I setup to show only H2's

<nav>
    <h3>Contents</h3>
    <ul>
        {headings.map((h) => {
            if (h.depth == 2) {
                return (
                    <li key={h.id}>
                        <Link href={"#" + h.id}>{h.value}</Link>
                    </li>
                );
            } else {
                return null;
            }
        })}
    </ul>
</nav>

Code Syntax Highlighting

Nextra comes bundled with Shiki, so any code fenced in markdown will automatically be marked up. I just need to add a bit of CSS to style it. Check out Steve Kinney's Custom Shiki Themes with CSS Variables. Here's the CSS I use for the colors on this site.

:root {
    --shiki-color-text: #fff;
    --shiki-color-background: #0d0d0d;
    --shiki-token-constant: #eaa5ea;
    --shiki-token-string: #99cc99;
    --shiki-token-comment: #999999;
    --shiki-token-keyword: #ffce9a;
    --shiki-token-parameter: #6699cc;
    --shiki-token-function: #59a8f7;
    --shiki-token-string-expression: #99cc99;
    --shiki-token-punctuation: #ffead8;
    --shiki-token-link: #66cccc;
}

Redirects

If you deploy on Vercel, Nextra is just Next.js so it supports the same redirects defined in vercel.json, see documentation here.

Redirects are defined in an array like so:

{
    "redirects": [
        {
            "source": "/legacy/:path*",
            "destination": "/new/:path*"
        }
    ]
}

Components in MDX

Using MDX, you can mix in React components alongside normal markdown. Check out the available built-in components in Nextra.

I had trouble getting the mdx-embed package working, so I just wrote my own components for YouTube and CodePen.

I created the following theme/components/youtube.jsx:

export default function YouTube({ id }) {
    return (
        <div>
            <iframe
                className="aspect-video"
                src={"https://www.youtube.com/embed/" + id}
                title="YouTube Video"
                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
            ></iframe>
        </div>
    );
}

Then to use in any MDX page I add the import on a line of its own after the frontmatter:

import YouTube from "../../theme/components/youtube";

And to use add the component on a line of its own in the markdown:

<YouTube id="VNftLswHSoc" />