how to make a static blog with Remix and MDX

published Tue, Dec 17, 2024

This is a static site powered by Remix, Tailwind, and MDX. It's hosted on GitHub Pages and uses GitHub Actions to automatically deploy the site when I push up changes.

I learned Remix and made this site in a week! The first iteration, anyway. The source code is publically available; feel free to use the project as a starting point for your own site.

I wanted to try out Remix for this project. Remix is great for server-side rendering (aka SSR), but this site is totally static, so it's not really needed. I wanted to generate all the pages at build time (static site generation aka SSG) so I can host the site for free forever using a static host like GitHub Pages.

(note: there are platforms out there that offer free Remix hosting, but I had my heart set on building to a static site.)

I found an excellent script written by Habib Hinn (detailed in this blog post) to generate the pages, inspired by a post and demo project by Michael Jackson, one of the co-founders of Remix. At a high level, here's how it works:

  1. Build and run the Remix server locally.
  2. For each of the defined routes, make a HTTP GET request to the local server to download the page as plain HTML & JS.
  3. Save the downloaded pages in the client build directory with the other static assets, nested in directories as needed to match the route. The client build directory can then be statically deployed & hosted.

I updated the build command in package.json so that building the whole site is as easy as running npm run build. The resulting ./build/client directory is ready to deploy. But first I need some content... 🤔

I decided to write my page content with MDX which is effectively Markdown plus React. I like Markdown because it's easy to write — it's got decently expressive formatting options with a minimal syntax. With MDX, in addition to the standard Markdown features, I can also inject React JSX or HTML when I need something more custom or fancy on a page. This very page you're reading was written as an MDX file.

Thanks to the flexibility of Remix, I could also write JSX/TSX instead of MDX on a specific page if I want just by changing the file extension. For example:

app/
└── routes/
    ├── my-first-post.mdx
    ├── my-second-post.jsx
    └── my-third-post.tsx

Anyway, I added the MDX Rollup plugin and configured it by following these instructions in the Remix docs. I also had to define some styles for the HTML elements emitted by the MDX plugin since Tailwind overrides the defaults for those elements.

As Michael notes in his demo project, all links need to do a full page reload since there's no server running to handle client-side page transitions. He mentions using <Link reloadDocument> but I use <a> tags because I don't need to leverage the client-side router at all. The MDX plugin already emits <a> tags for links, so I only need to remember this when I'm writing a JSX or TSX page.

I've used GitHub Pages a bunch of times for super easy static hosting so I'm using it again here. I already had a public repo on GitHub for this project, so I went into the settings and configured my project to deploy from the root of the gh-pages branch. I built the static site locally, pushed it onto this branch, and voilà! My site was live 🙌

This works as-is, but I don't want to have to remember to manually build and deploy the site every time I update a post or something. Thanks to GitHub Actions, I can define a simple script (aka a CI/CD pipeline) to automatically build and deploy the site when I push up commits on the main branch. I added a workflow based on the one detailed in this article by Daniel Jimenez Garcia. It's pretty straightforward:

  1. Using an Ubuntu instance, install Node 20 and project dependencies.
  2. Statically build the site (using the npm run build command configured before).
  3. Initialize a Git repo in the build directory and force-push those files onto the gh-pages branch.

Note that because the gh-pages branch is getting a brand-new repo force-pushed onto it on every deploy, the branch is getting completely overwritten every time. I'm ok with that because I don't care about preserving the history of this branch. Thus I won't make any changes to this branch manually; if I want to make a change to the deployed site, it has to be in the project files, the build process, or the host config.

Finally, I bought my custom domain and configured it for my site by following the GitHub docs. I also enforced HTTPS and verified my domain for additional security.

Also, importantly, there are two files that need to exist in the root of the deploy source (gh-pages branch): CNAME and .nojekyll.

  1. When you first configure the custom domain, GitHub creates a CNAME file in the root of the deploy source which is used for routing.
  2. GitHub Pages assumes by default that your site needs to be built with Jekyll, which in my opinion is a silly assumption, but it is what it is. I ran into an issue related to this and needed to add an empty .nojekyll file to the root of the deploy source to prevent Jekyll from getting involved.

I added both of these files to the public directory in my project — that way, when the deploy action is triggered and it overwrites the contents of the gh-pages branch, the files are always copied into the deploy source.

That's how I set up this site! I'm happy with how it works so far and I have ideas for improvements, so keep an eye out for more posts. Hope this was useful in some way. Peace ✌

No rights reserved 2024