DEV Community

Cover image for Before building your next static site with React, consider this
Ben Holmes
Ben Holmes

Posted on • Updated on • Originally published at bholmes.dev

Before building your next static site with React, consider this

โš ๏ธ Warning: Potentially controversial opinions ahead! Turn back now if Gatsby is your lord and savior, and you serve to protect the React dogma.

So I just finished building my shiny new personal site โœจ I thought about using fancy, component-driven frameworks like Gatsby to stay on the bleeding edge of web development, but after some reflection, I thought to myself...

do I really need all this tooling to write some static HTML?

This ended up being a huge learning experience on how to build a statically generated SPA from scratch (check out the README if you're curious how I approached it!). But it also taught me some valuable lessons on how far you can go while ditching the component libraries we all know and love.

A little background: going from dynamic to static sites

Okay, let's be real for a moment: component-level thinking rules modern web development. Even as new frontend libraries and Ruby-esque frameworks hit the scene, they still rely on the same basic formula: write your markup and JS logic inside a bite-sized component, and compose those components with imports and exports. Whether those components are class-based, functional, or even at the DOM level (hello web components ๐Ÿ‘‹), they're all focused on these ideas of logic isolation and code reusability.

Popular libraries like React and Vue have become such a one-size-fits-all solution that we don't even question them anymore. New to building literally anything on the Internet? Just run npx create-react-app and get going!

...right?

I used to agree. But over the past couple years of Gatsby and JAMStack preaching, I've also come to realize that damn, we're making some fat JS bundles. With all these JS-based components, we're shipping entire rendering libraries to the browser, even for a company's plane ole' static splash page!

Before getting cynical, it's worth remembering why these libraries were made in the first place. React wasn't created because Facebook should be a better static site; it was created because Facebook is a super dynamic, ultra complex web app with logins, trackers, home feeds, settings menus, etc etc etc. That involves a ton of data / state management, which means, well, a whole lot of JavaScript to construct the web page. For this use case, it makes perfect sense to build and use a UI rendering library that's state driven, rather than markup driven.

This is why Gatsby (a popular static site generator) came years after, say Redux state management. Devs were mostly fascinated with building dynamic, JS-driven experiences that state, props, and data objects could solve. It wasn't until later that devs started wondering how they could bend these JS-heavy libaries to their will for static site creation.

If you ask me, it's pretty ironic that it takes a 500 MB directory called node_modules to generate a website with... as little JavaScript as possible.

Still, I can't say I'm surprised either. When you're taking a library like React, which needs JavaScript to render anything to the page, you'll obviously need even more JavaScript to process all that rendering logic to begin with. Fire really does fight fire.

So... why use React on a static site?

At first, it kinda feels like using a chainsaw to slice a loaf of bread. Why use a rendering library like React when you have zero-to-little rerendering to worry about?

In short, hydration.

Flash dance scene where water bucket falls onto dancer sitting in chair
If you don't get this reference, go culture yourself

For those unfamiliar, hydration basically lets us write a dynamic, state-driven webpage, but also render as much of the page ahead-of-time as possible using static HTML. The Gatsby blog does a great job explaining this process, but here's a quick step-by-step:

  1. Your app exists as a big bundle of components, much like a create-react-app.
  2. The static site generator comes along and renders out this bundle at build time. Now, instead of shipping a blank HTML file to the user, you can send the entire page's markup for a speedy page load.
  3. Now, we want to do some stateful component magic alongside the static HTML we just built. To pull this off, we can look at the HTML page that's already been generated and compare it against our tree of components. When we find a component that does some state management craziness, we'll slot it into our existing HTML without rerendering the entire page. In other words, we hydrate our markup with some stateful components.

Seems slick! This comes in handy when you have some JS you want to use (say, a crazy animation library for added spice) that only apply to small areas of your otherwise-static site. But as you might have guessed, we'll need to ship the entire component library to the client in order to compare against the HTML. So it's still a fat bundle... but at least the user sees something on the first page load ๐Ÿคทโ€โ™€๏ธ

And what if you don't need state management?

Now, React doesn't make as much sense. If we just need to handle some button clicks, we should probably just write a couple lines of vanilla JS instead of, you know, shipping the entire React library ๐Ÿ˜ฌ

For some perspective, here's some common dev requests when building a static site:

  1. We want to break down our static site into reuseable UI components that can accept some JS objects as parameters (aka "props"). This lets us, say, turn a list of blog post links into a bunch of clickable cards on our homepage.
  2. We need to fetch some information at build time to slap into our production site. For example, we can go get some Twitter posts at build time to slide into our site's homepage, without shipping any API calls or exposing secret keys.
  3. We need to generate a bunch of URL routes from either a directory of files or a fat JSON object of content. For instance, we have a folder of markdown files that we want to turn into a personal blog, making each file its own URL on the interwebs.

These are all great reasons to use static site generators. But looking at this list, only the first requirement actually involves a component library. And even then, we may not need to worry about rerenders or componentized state management; it's mostly done at build time! If only there was a way to make our markup reuseable and template-able, without shipping a bunch of unused JS...

(Re)enter: Pug

That's right, good ole' Pug (formerly Jade). You know, that cute little templating library you used on your last CodePen, or maybe the weird looking HTML you found on an Express server template. It's a mighty little library from a pre-React era, before component-driven state management was even a thing.

It also uses a simplified syntax for HTML that makes it a bit easier to type / look at, which I'm personally a fan of ๐Ÿ˜

So why am I bringing up this jaded (pun intended) templating library? Well, let's run through some of Pug's defining features to see what it can do. I'm hungry so we'll use a donut shop for examples ๐Ÿฉ:

1. You can take in some JavaScript data and turn it into HTML elements

This opens the door for all kinds of craziness, like looping, conditional "if" blocks, defining text content... you name it:

   main.krispy-kreme-menu
    if menuItems.length === 0
        p.alert Sorry! Sold out of gooey deliciousness :(
    else
            dl
            each item in menuItems
                dt #{item.name}
                dd #{item.price}
Enter fullscreen mode Exit fullscreen mode

And at the JavaScript level:

   const stringOfRenderedHTML = pug.render('/filename.pug', { menuItems: [...donuts] })
   // spit out this string of HTML into a .html file at build time
Enter fullscreen mode Exit fullscreen mode

2. You can compose multiple HTML files (now .pug files) into a single page layout

For example, you can create a navigation bar in one file...

   // nav.pug
   nav.krispy-kreme-navigation
    a(href="/") Home
    a(href="/donuts") Buy some donuts
    a(href="/contact") Somehow complain about your donuts
Enter fullscreen mode Exit fullscreen mode

... and import into another file:

   // index.pug
   html
       body
           include nav.pug
           main.donut-content
               ul.list-of-tastiness
           ...
Enter fullscreen mode Exit fullscreen mode

We can go even deeper by passing parameters / "props" between these files. Check out this mixin syntax:

   // nav-mixins.pug
   mixin NavBar(links)
    each link in links
        a(href=link.href) link.text
Enter fullscreen mode Exit fullscreen mode
   // index.pug
   include nav-mixins.pug
   html
    body
        +NavBar(donutLinksPassedDownByJS)
        main.donut-content
            ul.list-of-tastiness
Enter fullscreen mode Exit fullscreen mode

Here, we can consider each mixin an export statement from nav-mixins.pug. Then, when we include this file somewhere else, those mixins become usable via the + decorator (aka our "import" statement).

So in summary...

โœ… We can turn JSON objects into static HTML with a one-line script (pug.render(filename, someJSON))

โœ… We can break up our layout into multiple files using imports

โœ… We can define component-like "mixins" for reusability and data passing

...in other words, we get to make our UIs with components, but without sending a bunch of libraries to the client!

But wait, this idea is nothing new!

I know! Backend servers have been doing this for decades.

Let's consider the server-driven model you would use for, say, an ExpressJS app. Everytime you hit an API endpoint, the server may go look up some info from a database, roll that data into an HTML template (probably using Pug), and send it back to the user. Same goes for PHP, C#, GoLang, or whatever exotic server you've seen before.

But guess what? A static site generator does the exact same thing! The only difference is that now, instead of doing all the data fetching + templating in an API endpoint, we're doing it in a build script that we call when the website actually gets deployed. For those familiar, this is that fancy script you tell Netlify to run when you first deploy your site.

Servers used this templating model long before we were making crazy, ultra-dynamic web apps. So my question is this: when your website just has some static landing pages, a few lines of JS, and maybe a blog to generate... why throw away this idea of templating for component libraries?

Call to action ๐Ÿ‘‰ check out 11ty

I just found out about 11ty (pronounced "eleven-tee"), a static site generator built with this exact philosophy in mind. You can choose the HTML templating language of your choice (Markdown, Haml, Pug, Nunjucks, and a bunch more), and let the framework handle all the complicated routing and page generation for you. If you're trying to build a portfolio site with a blog, a promotional splash page for a company, or anything super static-y, this is honestly the best solution I can think of.

You can also fork the Pug-based framework my personal site uses if you're curious. It's missing some pretty major capabilities at the moment (nested routes and CMS integration to name a few), so proceed with caution in case you're so brave ๐Ÿ˜ˆ

That said, I'm definitely not suggesting you give up your beautiful Gatsby site! There are some serious benefits to their hydration model in case you want any state management stuff. Plus, if your super comfortable in React-land and don't have time to pick up new tools, it's a pretty convenient choice with a huge community of support. Same goes for Gridsome, a Vue-based static site generator that works in a similar way.

Anyhow, whatever you end up using, I hope this article got you thinking a bit more about what static site generators really do. With that, I'll leave you with this cute pug photo to stick in your mind ๐Ÿถ

Cute pug sitting on a bed

Learn a little something?

Awesome. In case you missed it, I launched an my "web wizardry" newsletter to explore more knowledge nuggets like this!

This thing tackles the "first principles" of web development. In other words, what are all the janky browser APIs, bent CSS rules, and semi-accessible HTML that make all our web projects tick? If you're looking to go beyond the framework, this one's for you dear web sorcerer ๐Ÿ”ฎ

Subscribe away right here. I promise to always teach and never spam โค๏ธ

Oldest comments (26)

Collapse
 
skd1993 profile image
Shobhit Kumar • Edited

Fantastic article Ben! Thanks for introducing me to Pug. Sounds a bit like Handlebars if I'm not mistaken?

Collapse
 
bholmesdev profile image
Ben Holmes

Thanks Shobhit! Yep, it's just another templating language. I know that Handlebars is very JavaScript-y though, where you need to write helpers in JS that are thrown into the rendered template.

Pug is super composable on its own, since you can basically write "components" using the mixin and includes syntax that I mentioned. If you're gonna ditch your component library for this sort of setup, Pug is my fave!

Collapse
 
jimafisk profile image
Jim Fisk

Great post Ben, couldn't agree more! I've pushed Hugo to break up components in a similar way and it worked well, but you have to use go templates. I need to spend some time with 11ty! Also cool to see your Pug project, thanks for sharing!

Collapse
 
bholmesdev profile image
Ben Holmes

Oh yeah totally agree, I think Hugo has an interesting approach. Also cool to see the static site generator you're working on is built on Svelte! Should have a way smaller bundle size over React. Cool project to follow if you need that component-based framework with hydration ๐Ÿ‘

Collapse
 
omrisama profile image
Omri Gabay

Pretty neat. Was about to say that Out templating looks VERY similar to Haml

Collapse
 
bholmesdev profile image
Ben Holmes

Yeah I noticed nunjucks in their docs! Decided to talk about Pug here since it had my heart since my CodePen days, but that looks like a super capable templating lib too. Glad to hear your success with 11ty btw!

Collapse
 
mykalcodes profile image
Mykal • Edited

This is an awesome post! thank you, Ben!
I've had a lot of similar realizations lately while building out static sites. The developer experience of React and similar JS frameworks are great, but at the end of the day, we should be optimizing for the user's experience, not our own.

Tools like 11ty do get you close to the same DX, and with some tuning, I'd argue you can get even better DX than you would with most react setups (throw a dash of Webpack/Parcel in there, some Typescript maybe, Preact.js or Svelte to keep the bundle size small if you really need something dynamic. )

The more I learn about software development, the more relevant "choose the right tool for the job" becomes. I think for static sites you hit the nail right on the head. Solid read ๐Ÿ™๐Ÿป

Collapse
 
chipp972 profile image
Nicolas Pierre-Charles

Ok so if I understand well, you make the case for using no framework when building mostly static website that don't ship much dynamic content and interactions.

I don't understand why is it so important to reduce bundle size to a minimum if, anyways, loading the framework is not required to interact with the page (if you used pre-rendering/server-side rendering).

Plus, I think you overlooked the fact that declarative components are easier to maintain and make evolve than imperative DOM manipulations. Maybe you'll save 0.5~1 second of the life of each of your visitors but it will take you 2x or 3x the time when it comes to adding a new feature after some time (in my opinion).

Still a well written articles with good examples though. Thank you for sharing.

Collapse
 
andrewchmr profile image
Andriy Chemerynskiy

Hi, enjoyed your article! I wanted to get report of your website via Lighthouse (btw it has high score) and found one thing that might improve SEO :)

Collapse
 
bholmesdev profile image
Ben Holmes

(sorry about my previous response. Thought you meant the SEO on this blog post haha).

Thanks for the callout! Thought that providing og:description would also cover the description tag. Updated to get that ๐Ÿ’ฏ SEO goodness

Collapse
 
andrewchmr profile image
Andriy Chemerynskiy

Haha, my strategy for SEO is to rather have more metadata than less. If I don't know if some meta tag is redundant or not - I add it anyways :D

Collapse
 
kartiknair profile image
Kartik Nair • Edited

Great post! I'm actually working on Dhow right now, a Node.js SSG that lets you write JSX syntax (similar to next.js with getProps & getPaths functions) but generates pure HTML. It's currently a work in progress but I would love to hear what you think!

GitHub logo kartiknair / dhow

JSX-powered static site generator for Node.js

Dhow

npm version License

JSX-powered SSG for Node.js. Write logic like React with a directory-structure like Next.js but generate plain HTML with no client side JS.

A demo of what it does

Getting Started

Getting started is very simple. You can use the create-dhow-app npm package to quickly bootstrap a project based on a template.

npx create-dhow-app my-app # Optionally specify a template like this: `--template blog`
# For older versions of npm
npm i -g create-dhow-app
create-dhow-app my-app
Enter fullscreen mode Exit fullscreen mode

The default template will show you the basic structure of a Dhow app but using something like the blog template will show you everything Dhow can offer.

Create a project from scratch

If you would like you can also create a project from scratch without using create-dhow-app. Let's walk through it.

# make a directory for your project
mkdir my-app
# change your directory
cd
โ€ฆ
Enter fullscreen mode Exit fullscreen mode
Collapse
 
martin2844 profile image
martin2844

What about Gatsby image? Maybe my site could have been probably done with html and vanilla js, but how can I get my sweet blur lazy loading? Honest question

Collapse
 
aralroca profile image
Aral Roca

I think Next.js is very good for this. Without almost any tooling you can just do next build && next export to generate the static site. And then you have the option to switch to dynamic by doing next build && next start. And for the dynamic version, if you are worried about React size you can just replace it to Preact (3kb with the same api).

Collapse
 
bholmesdev profile image
Ben Holmes

Yeah, thanks for pointing that out! Next + Preact is a super powerful combo. I've heard some complaints about library support on Preact, but the community's still pretty robust at this point. Worth checking out!

My only complaint is that the developer environment is dynamic (by design of course), and you won't know how the static site actually performs until you export. This can lead to somewhat longer build times from my experience, and certain pages of the site not getting crawled properly. That said, for smaller scale projects, it shouldn't matter too much.

Collapse
 
toheeb profile image
Dr SU

Well said!

I had the same thought a while ago.. First thing that scared me off was the size of the node modules. Second was the build process - slow! Third was some react compatibility issue I encountered when installing the modules.

For someone trying to create a blog - I believe that's an overkill when there is 11ty!

Collapse
 
weedshaker profile image
weedshaker

Very nice and refreshing article. Slowly but certainly, voices like yours are surfacing... I always missed the simple times and believe that they are just around the corner with web components. Those donโ€™t care, if they are loaded in a static context with itโ€™s innerHTML present or if โ€œon connectedโ€ they find their inner structure is missing something and fetch /render by convenience. This will make it no effort to prerender and hydrate as its natural behavior but then they are extremely light and super fast in the DOM as native web api, that you may find the conclusion => that the site is fast enough. LOL ๐Ÿ˜‚

Anyhow, I work on an event driven solution making lots of things easy and stuff like state machines obsolete, all vanillaJS without dependencies: github.com/Weedshaker/event-driven...

consider this for your next project. ๐Ÿฅณ๐Ÿค™

Collapse
 
cauerego profile image
caue rego

not a single mention of "offline" in the article, comments or sign up process for "dev.to".

big bummer!

i never used gatsby, and i'm not really well versed into most of the web coding platforms of the past 5 years or so... (is this hydrate thing exactly the offline feature i am mentioning below?)

but gatsby website works amazingly well offline, like every website should!

the test is very simple. load website (on android, in my case). turn off internet. reload page.

at very least what's visited should work!

(i would argue that a small website should load everything in the first hit, and i would bet gatsby can do that easily too... or at least i hope, since i'll still probably migrate from jekyll there, even after this article to which i can certainly agree... because...)

sadly, 11ty or any other related link i could find, doesn't (except mozilla nunjucks work a little bit). just another "fuck offline first" broken mentality, imho.

also... i don't really get the "better and faster" for small sites... even jekyll can score 100% for those sites in every possible test there is. so what's really the point?

not to mention both the language and the pug dog are ugly as the devil! ๐Ÿคฃ

by all means, have fun with them...

(i know i'll soon get my own fun by deleting facebook, perhaps inspired by the 11ty affiliate... not because of privacy concerns at all, as you can read on cregox.net/privacy nor because whatever they do wrong in policies... it's just because most of their products are truly bad as a product on their own. in a similar fashion, i'm tempted to delete google as well... they always made fantastic products, but they've been losing their touch... plus, both of them lack this one other fundamental part of the internet: making my data redundant automatically, which means outside of their reach, for real backup... and better options are starting to appear, such as secure scuttlebutt).

just please, implement some offline code already! and ideally some pwa along, you know, to easily "install" or download everything for later offline usage with intent, or simply for bad connection.

Collapse
 
bholmesdev profile image
Ben Holmes

Ohhh I wouldn't say that! I agree that Gatsby can be a bit heavy-handed for certain projects, but it strikes a neat balance between stateful clientside logic and static rendering. For that reason, I think it's basically a replacement for create-react-app for full-scale projects. It's also great for running your API calls ahead-of-time so you don't ship API keys to the client.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.