AI-generated sketch images of React and Vite's logo together, arranged in a grid in the style of Piet Mondrian's Composition II in Red, Blue, and Yellow

Writing static websites with Vite and React

Carlos Neves

TL;DR

In this post, I talk about vite-ssg-react, which is an attempt to configure Vite so that we may use React to write static websites. E.g.:

Given the following input:

src/
├── asset/
│ └── react.svg
├── component/
│ ├── Footer/
│ │ ├── Footer.css
│ │ ├── Footer.jsx
│ │ ├── index.js
│ │ └── script.js
│ └── NavBar.jsx
├── foo/
│ └── index.html.jsx
├── index.css
├── index.html.jsx
└── public/
    ├── robots.txt
    └── vite.svg

It should produce the following output:

dist/public/
├── asset/
│ ├── index-BX-kPJX6.css
│ ├── react-CHdo91hT.svg
│ └── script-CDOjA-R4.js
├── foo/
│ └── index.html
├── index.html
├── robots.txt
└── vite.svg

This input-output structure is taken from the template project, which you can bootstrap locally by running:

npm create vite-ssg-react@latest example.com
cd example.com
npm install
npm run dev

(Replace npm with your preferred package manager.)

We keep the 3 expected base commands:

Why do this though?

Vite is an awesome bootstrapping tool, but the assumed use case is that you're writing a SPA. What if we had a similar tool to bootstrap static websites? Where each entry point file (index.html.jsx in this case) "renders" onto an HTML file, and then we just serve those. There should be no "router" abstraction. The routing framework is simply HTTP & HTML. Moreover, we should be able to deduplicate repeating components as functions. It's in this effort that I've been hacking together vite-ssg-react. Although I'm using it to render this website, it's very much experimental and a WIP at this moment.

I see much hate about React/JSX in many places. I still think its pros outweigh its cons. Let me elaborate on three of the bigger points for saying this. And before saying anything, we can acknowledge that today most other frontend frameworks/DSLs have caught up with these pros, so there's probably a bigger article here about writing "Turing-complete", declarative interfaces... React/JSX is just the one with which I've had the most affinity.

I say "React/JSX", because I mean the whole DSL of embedding HTML/XML into JS. That's the first strong point for me. Now we have the whole programming language to manipulate data and map it onto the tree-like XML structure of the UI. No compromises (well... at least in theory). No trying to go the other way around and embed the programming language into XML with <if>, <for>, etc. Ironically, one of React's shortcomings is not being able to easily embed if, switch, and for inside of the HTML syntax. We're forced to resort to logical expressions, ternaries, and the map function. Curiously, it seems clay allows for these syntactical constructs without compromise; making it a kind of "perfectly imperative" cousin DSL for writing UIs.

The second strong point is the idea that "the view is a function of the state": v = f(s). Basically, we divide our code into functions. Functions handle deduplication, compose easily, and may even be recursive. Now, of course, in any real-world business system, you're likely going to break this idea in many ways. I guess it's more about remembering it as a guiding principle. It seems purely functional components have their proper place within many systems.

The last, yet not the least, point is the tooling ecosystem around React. It definitely has its "rabbit hole" dark side, as you may find yourself spending an unjustifiable effort configuring it; or you simply roll with the defaults prepared by the community (by using, for example, Vite or CRA before it). For me, the tooling comes together at the faster development feedback loops it has enabled.

Consequences of pre-rendering static websites with vite-ssg-react

During development, it's using SSR to render the HTML and send that to the browser. The cool thing is Vite's hot-reloading somehow continues to work for JS and CSS edits. As of now, vite-ssg-react depends solely on react-dom/server, and not on react or react-dom/client. After implementing this behavior, however, I realized there might be subtler work to be done here. Which one enables a faster development cycle: SSR or client-side rendering as usual?

Keeping both development and production-build workflows using SSR simplifies the situation regarding React Hooks. Hooks don't offer much utility here, because the whole of the rendering is done in one pass, and there's no client runtime; there's no hydration. There's no "component lifecycle", the "components" here just serve as functions that output HTML.

When building for production, firstly the SSR backend is built, and then this very backend is executed to render the final HTML.

Some consequences of doing this are:

Next steps...

With all this being said, there are still bugs and shortcomings with vite-ssg-react. And with the code for this blog... But for now it should be enough as a starting point.

I'll leave here some ideas to further improve vite-ssg-react:

For this blog, I still want to do:

Thanks for reading.