Gonna keep this one relatively short. I'll highlight what was wrong with my first attempt and then dive into how I resolved the issue.

First Attempt

Setting up Vercel was as easy as "New Project" and it recognizing my repo as a project. Below is the very first thing I saw when I visited my Vercel site!

Error: ENOENT: no such file or directory, scandir '/var/task/output/server/pages/blog'

So I dug around the Remix docs & Discord server. Long story short, Vercel only uses the build output. My blog folder is not a part of that build output.

Adding GitHub

To get around the limitation of not having access to my content files, I'll use the GitHub API to read my content. I added app/util/github.server.ts to manage calls to GitHub.

import { Octokit } from "@octokit/rest";
import invariant from "tiny-invariant";

const octokit = new Octokit({
  auth: process.env.GITHUB_TOKEN

export const getBlogPostDirFiles = async () => {
  const { data } = await octokit.repos.getContent({
    owner: "vivalldi",
    repo: "",
    path: "blog"

  invariant(Array.isArray(data), "Expected data to be an array");

  return await Promise.all( fileData => {
      invariant(fileData.download_url, "Download URL is missing");
      let resp = await fetch(fileData.download_url);
      let content = await resp.text();

      return {

export const getBlogPostFile = async (slug: string) => {
  const { data } = await octokit.repos.getContent({
    owner: "vivalldi",
    repo: "",
    path: `blog/${slug}.md`

  invariant(!Array.isArray(data), "Response malformed");
  invariant("content" in data, "Response not a file");

  return Buffer.from(data.content, "base64").toString("utf8");

I also moved app/blog.util.ts to app/util/blog.ts. This just helps tidy things up.

The notable changes to blog.ts are that I call getBlogPostFile to get the post data rather than fs. The same goes for listing blog posts.