Skip to Content

How do I manage this blog's content remotely?

In this post, I will break down the workflow I use to manage this blog's content remotely, why I overengineered an overthought-underused blog , and finally, how I was able to (almost) achieve the near-perfect setup I wanted.


Here's the source code for the "plugin" that allows to use Git as a source for Contentlayer. It will clone and sync the content to your machine. (If your content is in a private repo, you'll need to set up a personal access token).

Let's start by:

Why did I choose to go this way?

Last year, I got myself thinking multiple times about a better way to manage the content I create. I always loved to write, and for that, I had already tried Medium , and LinkedIn, but those still don't feel like the places I want to have my content. And that leads us to a quick tangent:

Blogging platforms

There is inherit problem with blogging platforms, and that is that they are built for thousands of users, for the plans of the company, for every topic ever, and this makes them lose focus, getting bloated and filled with useless content and features.

Medium turned into a paywalled platform that blocks out users from content created by other users, and LinkedIn is still a social network, a weird one and a Microsoft one.

The plan

After that, I decided to give my website a (more than needed) facelift. So to start on this ongoing task , I decided to sit down and list my options:

And with all that set, we go to the next step:

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 is a tool that allows you to use your content as data, and it's a perfect fit for what I wanted; it has a great integration with Next.js, and it's pretty simple to use, but it had a problem. Let's look at the supported content sources:

Contentlayer Supported Content Sources

Well, that's not what I wanted; I wanted to use Git, and I wanted to use MDX, and I wanted to use them together, so...

Thinking GIF

Contentlayer has a way to add new content sources, and a file-based one. This sparked an idea of how to make it work. I could use Git to manage my content, then use Contentlayer to read it, and then use it on my website.


Just to clarify the next section, this is not a tutorial on how to use Contentlayer. If you want to learn how to use Contentlayer, I recommend you check their (beautiful) docs.

All of that said, let's see how it works:


The code in this next section is a simplified version of the code I use on my website; this makes it easier to understand, but it's not the actual code I use. The whole thing is linked below.

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

import { exec } from "child_process"
import { promisify } from "util"
const sh = promisify(exec)
const sync = async (dir: string) => sh(`cd ${dir} && git pull`)
const clone = async (dir: string) => sh(`git clone ${SOURCE} ${dir}`)

promisify is a "node:util" function that converts a callback-based function to a promise-based one. This is a hacky way to asynchronously execute shell commands.

And now for the actual syncFiles function:

const syncContentFromGit = async (dir: string) => {
  try {
    awaitaccess(dir) // Check if dir exists
    awaitsync(dir) // Sync if it does
  } catch (error) {
    awaitclone(dir) // Clone if it doesn't
  return () => console.log("\nSyncing cancelled!")

And is that it? Well, pretty much, it ends up that Contentlayer has an awesome API that allows you to extend and customize it in any way you want. Now, for the last step, how do we use it?

export default makeSource({
  syncFiles: syncContentFromGit
  // ...

And that's it. Now you can use Git to manage your content and Contentlayer to process it, so you can use it on your website. As I said for the Contentlayer setup, there are some more steps, but I'll leave that for you to check on their docs.

Final thoughts

I'm really happy with the setup I have now. I can use Git to manage my content and Contentlayer to process it; I can use it on my website; and I can use it to do anything from discovering the most used words on my blog to training an ML model to substitute me on my time off.

But as with anything in life, there are some downsides. The biggest one is that I need to push my content manually. This is not a big deal, but it's something I need to remember to do, and I'm not a big fan of that. I'm pretty sure Obsidian can help me with that since it can use a repo as the source, but I haven't tried it yet.

Other than that, only the CI/CD integration on Vercel is something that works flawlessly with the website, but the contents are not on the same repo, so I need to manually trigger the build, and I know there are ways to automate that, even with GitHub Actions (I think), which is something I'll look into in the future.

And I think that's it. I hope you enjoyed this post, and if you have any questions, feel free to reach out to me on X or LinkedIn 👀.


  1. This was my choice for a while, and I still like it the most. It is simple, fast, and reliable, but as we discussed before, there are some issues, and in this case, the content gets even more intertwined with the code. Next.js uses the MDX files as the pages following a Layout.tsx file; this is awesome for simplicity but not so much for flexibility. Any new section I wanted to add, like a TOC (summary), was already out of scope.

Clouds: A little UI meets 3D experiment

Get notified!

I promise to send you updates with the best content.
I hate writing emails as much as you hate spam.


A dead-simple React 19 "framework" implementation from scratch



Think "docker stats" but with beautiful, real-time charts into your terminal. 📊



a htmx like experience in Next.js with RSC and quite a bit of questionable code.



Supercharge Your VTEX IO Workflow with a Lightning-Fast CLI Alternative. Enhance Developer Experience and Boost Productivity.



Uma stack sob-medida escrita em Rust para os desafios da rinha e somente isso. Incluindo load balancer, servidor HTTP e banco de dados.



Expose specific colors from your Tailwind CSS theme as CSS variables

CC BY-NC 4.0©rafaelrcamargo.