Making the Remix Austin Blog

By Matt Hernandez

Apr 30, 2023

The word "Blog" over a blurred image of a code editor

So how did we get here?

Well, it starts a little before I got here. If I'm going to play historian, I'll say it all began with a discussion. As a subset of the greater Remix community, there was talk of what exactly this site was going to be other than a place to highlight the date of the next Remix Austin meetup. What kind of greater impact did we want to have on all the Remixers out there?

The rumblings of having a blog began back there, but they really started to move from idea to conception when our good friend Colby White decided to chime in with a plan. A few things began to take shape at that point, some more discussion, an early PR with mock blog content. But from what I can tell, nothing solidified just yet.

Where did I come in?

A month after Colby White's discussion of what a blog implementation of Remix could be, that's when I showed up. As a developer trying to get back into the React community after spending 3 years doing nothing but Angular at my full time job (seriously y'all, Angular is not cool and I want out 😅), I needed a way to brush up my old skills.

I went to a few React meetups in the Austin area, and through the word of mouth heard about the Remix Austin meetup. I showed up, heard some awesome talks, hung out with cool people, and figured this was a good way to get back in it.

Steal like an artist

I'm a big fan of using a file system as a CMS, and the concept has been around for a while now. I especially like file-based CMS-es for proof of concept work on blogs that are just getting started. This site is the perfect fit.

I was happy to see in Colby's discussion that he also liked the file system approach to a degree and had gone down that route when making his own website. He used .mdx files to write markdown posts with a little bit of React flavor.

You might be thinking, "Well problem already solved! Remix has support for .mdx files!"

And you're right, but even Remix acknowledges in their documentation that their implementation is not designed to scale and that it's better to use mdx-bundler to build content. A few people in the Remix community have already made their own creative solutions to this problem. These solutions work, and Colby's solution, inspired by this Remix stack, is in the Github repo for his website.

Colby White's website
Pay Colby's website a visit if you can

So my next move was simple: steal his work!

With credit, of course! But hey, don't repeat work that's already been done for you. And thanks almost exclusively to my timing in this project, I was the unlikely benefactor of Colby's hard work Hehehehe. I branched off a previous PR for an initial blog implementation from Seth Davis and then got to work. I explored the Github repo for Colby's website, found the pieces concerned with parsing .mdx files into a consumable format, and then whole-hog copied and pasted that stuff over.

I gotta admit, during the whole process, I only had about a 50% understanding of how everything worked. I just kind of let the compiler act as my guide. Module not found error? Great! What did I not steal that I was supposed to? Let me go back to Colby's repo and find out.

With about two hours of copy and paste, some compiler errors, and a few mock blog files, I was able to get an initial working solution in our site.

We got the goods. Now what?

The solution technically worked, but it was lacking quite a bit. The first being understanding. I'm no fan of "copy and paste" into production code. Sure, publicly available solutions are great starting points. But you shouldn't put any code into a codebase that you don't understand.

I took a good long look at what I copied and got my head around how it worked. The basic idea was this: there's a NodeJS script that takes all of the .mdx files and any of their dependencies and compiles them using mdx-bundler. The final result is a JSON array, with each post as an object in that array. Those objects have some frontmatter and a raw source string for a React component. That raw React component is the full content of the blog post.

The array then gets written into a file called post-cache.json in our /public directory. Because it's in public, it's available to all loaders. Loaders for the blog pages fetch the file, pick out the data they need, and pass the raw React component source to mdx-bundler which returns it as a regular React component. Finally, after all that, we display it in our page like good ol' JSX as a complete blog post.

Oh, and one more thing: all .mdx files are watched by Node when we run this script in development mode. If any of their content changes, post-cache.json is regenerated with the updated contents.

A photo of Matt's face looking very confused
Confused like my face here? Just reread that section a few more times.

This is a lot, right? And in fairness, it works pretty good. But it could be better.

For starters, the entire post-cache.json file was being regenerated in dev mode for any single change to a blog post. That means every blog was getting built and compiled even if I'm only focused on editing one post. That's a lot of wasted power. Ideally, I would like for my app to compile the one post I'm working on at the moment.

Secondly, the post-cache.json file itself was capable of getting quite large quite fast. There's a lot of boilerplate that goes into that raw React component for each compiled post. Imagine duplicating that boilerplate for every single post! It quickly bloats in size. Did we really need to have an array with ALL of the blog information in it (front matter and content)? It seemed better to have an array that just contained each post's front matter. That would then allow us to list all of our blog posts in the /blog page, without having to worry about all that boilerplate.

Lastly, if each compiled blog post has a lot of boilerplate, it seemed like a better idea to have each blog post built on demand by the server when the post is requested. That would save us from having one mega-file with everything in it.

These are all good things to fix, but they take some serious thought into how to do it right.

Where to begin?

First thing was to start building each post on demand. In order to do that, we had to do something kind of odd. We moved the .mdx files and any of their dependencies, which could be .tsx files or images, into a place that is accessible in the server at all times. That place is /public.

We installed sync-directory and had it watch our blog post directory. Whenever we would make a change of any kind, sync-directory would make sure to mirror the contents of our blog post directory into an equivalent directoy in /public. This means that in our app code, we can perform a simple fetch to each .mdx file in /public because now all our blog posts are available as static assets.

After we get the blog post content from fetch, we pass the contents into mdx-bundler along with a directory path that points to the blog post's dependencies. mdx-bundler then compiles the post, gives us the data we need, and we inject it into the page (you may be asking why we don't just read the blog post content directly using fs. We'll get to that later).

So now we have our blog posts being built on demand. But we're missing something.

If you could hit refresh, that would be great.
We need that refresh functionality, man.

Our blog post source doesn't live with the rest of our code that is watched by Remix. So how do we get auto-refresh when blog content changes? Turns out, it's super easy. In remix.config.js you can specify a watchPaths attribute with globs for extra files that you would like Remix to watch. If any of them change, Remix will auto-refresh. Now changes show up on demand when we are editing posts in dev mode!

And that's that!

So what did we learn and what do we still have to do?

Well, the Remix community doesn't seem to have an established solution for making a blog at scale. In fact, that's what Colby White's original discussion was trying to solve. And while our solution can be added to the list of other ways this problem has been solved, it still has some rough edges.

The biggest one is that our solution only works if we have access to our file system on the server. If we run our app serverless, we're in trouble. We're currently fetching blog post contents using fetch, which is good because if our /public folder was ever hosted on something like a CDN, we could just point the fetch to the right URL and we're golden.

But remember, our blog posts can have dependencies, which could be .tsx files, images, or anything else that can be imported like a module. If we can't find those files locally on our system, we would have to host those on a CDN and download them as well. That would turn each blog post into a cascade of fetches at one time. Anyone who has had to wrangle multiple dependent async requests knows how much of a pain it can be. Maybe at that point we resort to an "ahead of time" build setup. But that has trade-offs too, mostly the large file size of compiled posts.

These are problems we're eager to solve and we're already chewing on possible ideas.

Maybe this is where you come in

We only got to this point thanks to the efforts of so many people, from Seth Davis' original PR, to Colby White's musings about Remix and CMS-es, to stacks already built by people, to me integrating it all into one cohesive whole.

We're interested in taking these lessons and using them to impact all of the Remix community, not just the one here in Austin. We'd ideally like to make a utility library or stack that can act as the solid solution we wish we could have had.

If you're interested, please let us know via our GitHub or find some of us on our socials. We'll be improving what we have in the meantime. We hope you're there to see it!

Matt Hernandez

Matt Hernandez is a UI developer in Austin, TX. Originally a film school grad, he made his way to working as a dev when he realized that working in Hollywood was not all it cracked up to be. He's worked across all layers of the stack, but considers the front-end to be home and React to be the best UI library so far. You can find him on his Github or on LinkedIn.