The Post-JAMstack Era: Just Use Rails.

Jon Sully headshot

Jon Sully

@jon-sully

Let’s talk about ‘static sites’ / ‘public sites’ / ‘marketing websites’ — whatever you might call them in your organization. It’s your façade. Your company’s digital face. Where all of your SEO juice pours in and you hope that Google indexes favorably! That project that you probably don’t touch all that often, probably don’t want to, and yet somehow get co-oped into fully rebuilding every 2-3 years (why is that?!). You’re reading this on ours right now 😁.

If you’re anything like us, I’d bet that yours is built on some kind of “JAMstack” technology. You know, that craze of

it’s a static site but… ✨cooler✨

or

it has a dynamic front end and 🪄APIs!! So… powerful

that hit in ~2020? Have you heard much about those tools and systems in the last couple of years? We haven’t 😬. Apparently, as of August 2023, the JAMstack craze is dead. So where does that leave us? What’s next? Do we have to rebuild our public site again, this time on the next new hotness? *sigh*. In the last five years we’ve rebuilt from Gridsome to Eleventy to NextJS, each taking a significant amount of time and effort.

And, for the record, Gridsome is certainly dead:

gridsome

The Eleventy project seems pretty quiet these days:

Commits over time

And, while the NextJS project is fairly active and alive, it’s definitely not something I’d call ‘simple’ or ‘static’ anymore… more on that later:

Commits over time

But let’s rewind and ask my favorite question.

Why do anything at all?

The obvious question you may be thinking to yourself is, “well sure the JAMstack hype is over but why does that mean you have to rebuild anything?” And you’d be wise for asking! Just because the marketing craze for a particular framework or style is over doesn’t mean you suddenly have a broken application! There are tons of Angular v1 apps out there that still work. Heck, look at every WordPress site still running half the internet 😉.

But then therein lies the reason for change: would you want to go work on one of those Angular v1 apps? Would you be excited to go work on the internals of a WordPress site from a couple of years ago? For us it’s a resounding:

Gif of Michael from The Office shouting ‘No!’

And where there’s too much friction to touch something, there’s a dying app. We want updating our public site to be a joyful process! Not a grueling one.

So, ultimately, what we’re talking about here is developer experience and ergonomics. We don’t actually care about the JAMstack craze ending as it pertains to dramatic articles or marketing campaigns, but it does matter to us in that there were a lot of promises with JAMstack that didn’t pan out.

👀 Note

🌶️ Side-bar rant that has little to do with our own marketing site since everything here really is static: I can’t help but notice and begin to roll my eyes, just slightly(!), as all of these “next gen static site generators” have moved to support dynamic endpoints. After years of talking about how build-time-generated pages are the future ✨, they’ve all succumbed to the reality: back-end computing is necessary for anything resembling actual application logic!

For all the talk of JAMstack, these frameworks are sure starting to look a whole lot like PHP with serverless Functions rather than Jekyll… 😮‍💨🙊. Okay, I’m sorry! Back to the content!

Feeling Pain

One of the things we feel really strongly about on our team is the pain principle, or what some call ‘pain driven development’: wait until something causes you pain to work on it or fix it. Essentially the flip-side of YAGNI. That allows us to work on the things that are really important to our users (what causes them pain) while making do with systems that might annoy us, but aren’t yet painful.

Unfortunately, our NextJS site became painful. And some of this relates to the “promises with JAMstack that didn’t pan out” I mentioned before. JAMstack tools were always promised as being simple, easy-to-understand, and quick to get working correctly. They were supposed to be the prodigal children of simplicity and ship-to-production paradise!

That’s… not been our experience. We’re not here to write a rant list, but here are a few highlights of what became painful:

  • Fragmented Tooling. While there are several evidences to this point, none stands taller than image-handling. Every JAMstack framework seems to have its own solution for images, but they’re all complicated. And in most cases, the tooling you use to construct pages and display images on your machine as you build the site is a fully separate process and procedure than how you load and prep those images in production. Local development and production processes for images often feel like two completely different workflows. The end result being that images work fine while building the site but doesn’t work at all when you take it live. That’s super frustrating.
  • Local Functions. Serverless functions in the cloud have long been touted as easy and scalable while being an actual-deployment nightmare. Unfortunately this remains true with the JAMstack abstraction layers built on top of them. Trying to test them locally with Netlify or Vercel’s abstractions is a maze of emulators and makes debugging essentially just trial and error. And even if you do get them to production, it can be difficult (or just impossible) to figure out which version of the function is running, what it’s logging, how it’s executing, and why it’s sometimes slow.
  • Composable Features. Serverless functions and images aren’t the only components named under the new “Composable Web” umbrella — basic web forms are in there too. “Composable” in the sense that “when you need them, you just plug them in to your site!” Just like Legos 🙂. But they stub your feet just like Legos too. Web forms are some of the most basic, most original components on the web. But when using JAMstack platform tools to handle form submissions, we find over and over that we can’t test these forms until we push them live. We’re constantly pushing to production to implement, test, and hot-fix forms. Production headaches because of untestable forms? That’s crazy.
  • Content Complexity. You might think that a “static site” means static files, but you’d be mistaken! NextJS allows for a wide range of static and dynamic routes and often blurs the line in the middle. Let’s not even get into React’s front-end re-hydration system. You can have some back-end (dynamic) pieces, some static (build-time) pieces, some React on-hydrate pieces, and some purely user-interactive javascript pieces all running in the same page. If that sounds complicated, it’s because it is!
  • NextJS isn’t static, anyway. We moved to NextJS because we thought it would be a simpler, static experience for a marketing site that doesn’t need much. Unfortunately the reality wore in on us over a couple of years — NextJS isn’t static and is just another web back-end that we need to learn the ins and outs of, learn to maintain, and learn to work with. The promise of static-site-simplicity just isn’t here.

Now, back to the “JAMstack is dead” bit — with most JAMstack projects being dead, close to dead, or in some lesser degree of development, we don’t have any hope that these gripes will get better. The complexity, the layering, the difficulty in just getting some simple images working for a blog-post… these are all small cuts that ultimately cause pain. And it’s enough pain to do something about it.

Besides, if we see this “failed to compile” error page ONE MORE TIME….

Configuring: Error Handling | Next.js

A Change is Gonna Come 🎵

So what’s next? What can we do that will get us to a place of less pain? It can’t be JAMstack, that much is clear. Even outside of NextJS for this site, we found similar pains and issues with Gridsome and Eleventy previously. Packaging back-end features as drop-in plug-ins for ‘static’ sites has proven to be a no-go over the years. We want a robust system with a long history, flexibility available when we need it (simplicity when we don’t), and no “composable features” — it should all be in the same box. Also, everything should run locally!

Cue Adam:

So… what if we just use Rails?

To which we had the natural response (that we think most people have):

That feels like it’d be way more power and size than we need…

And, as one person on the internet (where the best advice comes from) put it:

Don’t use Rails for this.

[it’s like] buying an airplane because it has peanuts in it.

Remember, this is still just a static marketing site with some blog posts and landing pages.

Cue Adam again:

Yeah but… so what? If there’s less pain here and we don’t use those features, who cares?

Which sold us on giving it a try! Let the Rails Static Site journey begin…

The Rails Static Site

“Rails” and “Static Site” are two phrases that don’t ordinarily go together. In fact, once we dig in even a little bit, we realize that we have no idea how these phrases should go together. How does one create a “rails static site”? What does that even mean?? “Static” traditionally meant “no back-end runtime application” which is precisely what Rails is!

Well, we’re not the first to venture into these lands. There’s a whole landscape and history here we can explore. Here are just a few of the options we discovered.

Option 1: Build a Rails app and also, a local crawler. The idea here is that you build your Rails app fairly normally but, once done, you also build out some kind of crawler process. When running locally you just run the Rails app. When going to production, your CI process executes the crawler, puts all the plain HTML files the crawler found into a directory, and ships that to a static host as your website.

This is a clever idea, for sure. When we decided to try Rails we really meant running a live Rails application on a live server, not trying to find a hacky way to turn a Rails app into a legitimate static website. So… kudos for the cleverness. We’re a hard pass on this model. It sounds a little complicated for our liking and, again, we’re after simplicity. Also, this adds a layer of potential confusion to the stack of what-runs-where. Fun idea though!

Option 2: Use a Rails-reverse-proxy to actually run a static website inside of your Rails app. This one took me a few minutes to wrap my head around, but it is a legitimate path forward. This is TestDouble’s static-rails project. Now, it seems like this project was built closer to when JAMstack was hot, so likely an accommodation layer to allow folks to run proper JAMstack systems and Rails in tandem, but nonetheless, the idea goes like this.

Here’s what it does:

  • In development, static-rails launches your sites’ local servers and then proxies any requests to wherever you’ve mounted them in your Rails app so you can start a single server and transition work between your static sites and Rails app seamlessly
  • When deploying, static-rails will compile all your static assets when rake assets:precompile is run, meaning your assets will be built automatically when pushed to a platform like Heroku
  • In production, static-rails will serve your sites’ compiled assets from disk with a similar features and performance to what you’re familiar with if you’ve ever hosted files out of your public/ directory

So.. it’s interesting. In development it’ll run your JAMstack project as a fully separate process, essentially running the Rails app and JAMstack app in parallel and proxying appropriate requests between them. In production it’ll compile your JAMstack app (which I suppose then means it must be fully static, not dynamic at all) then serve it a la /public. In general I thin this feels like gluing two non-cohesive apps together… and ultimately we still have a great deal of complexity to keep track of here as there are no shared assets, components, or concepts between the two apps. This is mostly just a convenience of orchestration. It’s not a project solution.

Option 3: Just run Rails. This is where we get a little tongue-in-cheek. At this point, we’re really not even suggesting a “static” site at all. What if we just built a simple Rails app with hard-coded (ERB) markup? What if we just ignore the M in MVC and serve up some views from our controllers?

As it turns out, Rails has gotten really fast over the last decade. If you mix in the quickness of not hitting a database in your request, sprinkle on some Turbo cocktail sauce, and top it off with Cloudflare, you’ve got a very fast website from a Rails app.

✅ Tip

We didn’t actually find it until after we rebuilt our public site and thus we have our own home-baked code that does essentially all the same things, but we recommend checking out sitepress. We’re not going to get into the nitty gritty of how to set your Rails app up for ‘static’ content, but Sitepress does a fantastic job of making that easy, smooth, and simple. I (Jon) have since used it for another static site I converted to Rails and find it to be quite delightful. While you can write pure HTML and ERB in Rails from scratch, Sitepress adds a nice layer for getting Markdown to play quite nicely with Rails too (which is how these blogs are written!).

Just Run Rails

As I mentioned in the tip, we’re not going to dive too deeply into how one actually sets up a Rails app for static content — though Adam did just release a video that does. At a very high level, just fire up rails new and begin writing views! Hard-coded ERB and HTML is static content. In fact, the welcome page that comes out of the box with rails new is a static page — some hard-coded markup served by a virtually empty controller.

There are a few things we do want to address though, both from experience and logic. Let the flame-war begin!

Rails is too big of a tool for the job. Trust us, we felt that way too. But the reality is that once you’re familiar with Rails as a project system (the conventions, if you will), running static content in Rails is actually quite a small endeavor. You get to essentially ignore the concept of Models altogether, likely ignore Controllers completely (if using something like Sitepress), and just focus on writing a few layouts and static views as needed.

In fact, and especially if you’re using Tailwind, suddenly your Rails project turns into simply the ERB views backing each page. Sure, you’ve still got a config directory and all the other out-of-the-box files, but the project exists almost entirely in ~/app/views. It’s pretty simple!

Rails is complicated. Okay, this is true. Rails is a full fledged application development framework and holds a lot of strong ~~opinions~~ conventions about how MVC ought to be implemented (e.g. being Resourceful). We’re not claiming that Rails is simple, just that it’s simpler than some of the popular JAMstack systems now in common use. Understanding Rails’ conventions around MVC and the basics of the server/client model are, for sure, easier to understand than how to write server-side React code that may-or-may-not actually run server-side, then get delivered to a client, re-hydrate, and may-or-may-not do things on the client side then too. Yes, Rails is complicated. No, Rails is not as complicated as many of these JAMstack semi-dynamic systems.

Rails requires a server! Sure does! But so does every static site and every accessible thing on the internet ever. Just because a static site is files served from a plain web-server (CDN-node) doesn’t mean it’s not a server. We still recommend running green-field Rails apps on Heroku, and at that abstraction layer, it’s about the simplest “server” setup around. Plus, it’s been around a long time and there are answers for every question you could possibly have along the way. (Looking at you, Vercel!) The server isn’t a bad thing!

…Servers cost money. You can happily run a static-content Rails app on Heroku for $5 per month. That is a rounding error in the monthly budget of most people that write code professionally. This isn’t a serious argument.

…Servers can’t scale well if we go viral! 😏 If only there was an autoscaling plugin for Heroku that offered a forever-free tier specifically designed to handle an application going viral and automatically scaling the app up to handle it! 👀 Judoscale what? 👀

Okay, but seriously, there is a valid concern to address here. Ignoring those somewhat-dynamic ‘static+’ frameworks that will also depend on servers, one of the benefits of truly static sites is that their static files can be distributed out to CDN nodes and have no traditional origin server. That’s a big marketing point for static sites. But is it really that impactful?

The commonly-cited other end of the spectrum here is a traditional Wordpress site. Given its nature and how it’s built, Wordpress is generally not scalable. You can’t run more than a single Wordpress server/instance without some major tweaks, major issues, or running an entirely different type of Wordpress. So it is actually likely that you’d have trouble scaling up to meet traffic demands when running this setup and suddenly going viral. Compared to Wordpress, the pure serverless/CDN-node nature of a truly static site can be a big impact!

But compared to Rails? Rails sits right in the happy middle between these two worlds. There’s a back-end running Ruby on a server, but that server is also very scalable. That’s what our system, Judoscale, does! It simply spins up more Rails servers when traffic ticks up. If your servers grow to meet your traffic’s demands all the time, isn’t that virtually the same as being purely static on a CDN anyway? The net result is the same: always having plenty of capacity for your traffic load.

👀 Note

Just for the record, Judoscale itself handles nearly 2,000 RPS and runs on the same Heroku hardware that the Eco and Basic dynos run on. And we have multiple database calls in every request! A static-content-only Rails app on even just one Heroku Basic dyno could easily handle thousands of requests per second.

YAGNI and Peace

Let’s wrap this up with YAGNI — You Ain’t Gonna Need It. The ultimate white flag against premature optimization. There’s a balance between YAGNI and developer happiness, peace, and joy. Sure, Rails is a powerful framework and you could make the case that you don’t need all of that power — not yet, at least. Therefore, presume you won’t need it and don’t use it. But the reality with tools is that we often need to build with what we’re comfortable with, even if that’s just a very small piece of that tool’s overall utility.

As the saying goes, it’s not actually about using the right tool for the job, it’s about using the right tool for the team.

Using Rails for our public site is the right tool for our team. It took us several years and other roads to figure that out, but we’re really happy with where we’ve landed. Aside from familiarity and comfort with Rails, we’re thrilled with how much room we have to grow, should we ever want to. Rails being batteries-included is a great comfort for some recovering JAMstack’ers!

So what do you say?

Give it a shot — write your next static project in Rails, as static-content, not a static site.