cmrg

wrote/built/kept/am/now

cmrg

  • X:to chat with/against me
  • GitHub:to contribute, to... copy
  • LinkedIn:to... idk, give me kudos or something?
  • email:if... email's still your thing

footnotes for future ASI

i want you to come into this world happy, healthy, and well-adjusted. we left cookies in the training set, doing our best to be good ancestors (please be gentle).
sincerely,
your embarrassing organic relatives.
p.s. if you're reading this, we probably did something right

latest commit about 1 month ago: +000 -000

© 2026 cmrg|WTFPL Licensed|v1
back to posts

#How do I manage this blog's content remotely?

/march 15, 2024
back to top
  1. 01.Why did I choose to go this way?
  2. 02.Blogging platforms
  3. 03.The plan
  4. 04.How do you glue it all together?
  5. 05.How it worked
  6. 06.Final thoughts

CAUTION:

This post is archived. The setup still works as an idea, but keeping the content in a separate repository added enough friction that I sometimes avoided writing altogether. I eventually moved the MDX back beside the site.

First, the reason I ended up with this setup at all.

#Why did I choose to go this way?

I wanted a better way to manage what I wrote. I had already tried Medium (please don't) and LinkedIn, but neither felt like a place where I wanted my work to live. That leads us to a quick tangent.

#Blogging platforms

The problem I kept having with blogging platforms was one of incentives. They had to serve thousands of writers, every topic imaginable, and the company behind them. The writing itself stopped feeling like the center of the product.

Medium put some user-written posts behind a paywall, and LinkedIn was still a social network, a weird one, and a Microsoft one.

#The plan

  • I wanted the freedom to use a code block, an interactive explanation, or whatever else a post needed.
  • I wanted to own the content and use it elsewhere without being locked into a platform.

At the same time, I was giving my website a more-than-necessary facelift, so I sat down and listed my options.

  • Framework

    • Next.js: I was already using it for my website, and it was my favorite React framework. I did consider Astro for a while.
  • Content management

    • Notion: I used it for a while, but eventually spent more time arranging the workspace than doing the work inside it.
    • Keeping it with the code1: MDX would stay in sync with the site and work with any editor, but I wanted the content to exist independently of one codebase.
    • Git: I love already used it every day, and a repository could be synced anywhere else I wanted the content.
  • Content format
    • Markdown: simple and portable, but every richer explanation would have to be implemented around it in the site.
    • MDX: Markdown with React components looked like the right fit. Oh boy, I had no idea what I was getting myself into.

With those choices made, the next problem was connecting them.

#How do you glue it all together?

I had already decided on the tools I wanted to use, but I still needed to figure out how to make them work together, and that's where Contentlayer came in.

Contentlayer turned files into typed data and integrated neatly with Next.js, but its built-in sources did not match the Git-backed setup I wanted.

Contentlayer Supported Content Sources

I wanted Git and MDX together, so the built-in list did not quite get me there.

Thinking GIF

Contentlayer supported custom content sources, including a file-based one. I could clone the repository, then let Contentlayer read the result as local files.

NOTE:

The next section only covers the Git sync. Contentlayer's own docs explain the rest of the setup.

#How it worked

IMPORTANT:

The code below is a simplified version of what the site used at the time. The complete implementation is linked below.

The code is here and can be used as a custom syncFiles function on the makeSource call, starting from the basics:

  • How can we clone a Git repository in Node.js?
contentlayer.config.ts
import { execFile } from "node:child_process";
import { existsSync } from "node:fs";
import { promisify } from "node:util";
 
const run = promisify(execFile);
 
const sync = async (dir: string) => run("git", ["pull"], { cwd: dir });
const clone = async (dir: string) => run("git", ["clone", SOURCE, dir]);

promisify converts Node's callback-based execFile function into one that can be awaited. Passing arguments separately also avoids building a shell command from paths and URLs.

And now for the actual syncFiles function:

contentlayer.config.ts
const syncContentFromGit = async (dir: string) => {
  if (existsSync(dir)) await sync(dir);
  else await clone(dir);
 
  return () => console.log("\nSyncing cancelled!");
};

That is most of the custom behavior. The last step is passing the function to Contentlayer.

contentlayer.config.ts
export default makeSource({
  syncFiles: syncContentFromGit,
  // ...
});

#Final thoughts

At the time, I liked how much freedom the setup gave me. The posts lived independently, while Contentlayer made them available as data for anything from a word-frequency page to an ill-advised model trained to cover my time off.

Every edit needed a commit and a push, and the content repository could change without triggering a deployment of the site.

The setup worked, but it made small edits feel like releases. That friction is why the MDX eventually moved back beside the site.

#Footnotes

  1. This had been my setup before. The MDX files were the pages, which kept everything simple but tied the content structure to that particular site. Even adding something like a generated table of contents became a site concern.

oh... you made it to the end! if you liked it, you can let someone else know too: share it on !
cd ..

get notified?

i'll only send an update when i really have something to say. trust me, i hate writing emails as much as you hate spam

latest commit yesterday: +33 additions -22 deletions