Skip to content

Revenge of the JavaScript: Moving from Hugo to Next.js

Pull Request #711

I’ll say right off the bat: this website has a loooong history of going overboard with its tech stack. I use this domain as a vehicle to learn new things, and given how frequently the tides turn in the frontend development waters these days, things can (and did) get messy pretty quickly.

I discovered Hugo and started using it almost four years ago (6fff44e) in an effort to get back to the basics. Static site generators and buzzwordy JAMstack philosophies were all the rage, and the potential simplicity of having a website with HTML that I could have understood as a kid sounded fun and refreshing. Very quickly, I became a huge Hugo cheerleader, and I didn’t even add any JavaScript for over a year as a kind of challenge to myself. But as I saw certain JS frameworks and tools explode in popularity, some FOMO started setting in, and bringing things like React into the mix became incredibly tempting.

Turning to lighter frameworks like Lit and then Preact allowed me to tell myself I was still being a minimalist while dipping my toes in the component-filled waters. I added Preact (b755b66) to power the /projects page, the contact form, and the hit counter at the top of posts like this one. These components were made dynamic via serverless functions written in — you guessed it — JavaScript.

By the end of the Hugo chapter, I was using a cornucopia of build tools behind the scenes to keep everything glued together: Webpack, Gulp, esbuild, Babel, Terser, PostCSS, node-sass, imagemin... That’s a lot of JavaScript tooling for a site that’s allegedly avoiding JavaScript at all costs, and realizing this let me finally give myself permission to go all-in.

Enter Next.js, which caught my eye over other JS-centric SSGs (like Gatsby, 11ty, or Hexo) because it sounded like the best of both worlds. Next outputs a fully static HTML version of your site just like Hugo does and uses React to render pages dynamically (a bit like a single-page web app would behave). And once I learned how its incremental static regeneration feature works, I realized that my Hugo site was actually less static than it could be with a framework like Next! Pages like /projects, which pulls data from the GitHub API, had no actual content if you were to right-click and view source — but with Next, the project data actually exists in the static HTML initially sent by the server and is “hydrated” with the same data via React on the client-side. If you host everything on Vercel, this becomes even more seamless. (More on that later.)

My requirements for Next.js

  • Page sizes remain well under 1 MB. Minus images and third-party things like Twitter embeds, of course, I think I’ve achieved this on every page — the homepage still hovers around only 512 kB in total! Being super careful when choosing dependencies (Bundlephobia is awesome, btw) and making sure not to send any server-side code to the browser can make a huge difference.
  • All content and core functionality is still available without JavaScript. Go ahead and try it! Besides the obvious reasons, this was also crucial in keeping the 🧅 Tor mirror functional.
  • Fewer devDependencies and consolidated build tooling. I don’t want to look at another Gulp task for as long as possible. Next’s built-in Webpack and Babel support has come in clutch here.
  • Same (or better) Lighthouse scores. The heavier load of JS has certainly affected performance a bit, but any modern browser can easily keep up with any React code I’ll be using at this scale. And because of Next’s static page generation (and next-seo) nothing has changed in the realm of SEO.
Vercel web vitals analytics

Things I still miss from Hugo

  • Native Markdown support with shortcodes. I’m enjoying the React-ified MDX flavor of Markdown, but wrestling with @next/mdx, next-mdx-remote, and/or mdx-bundler adds a whole new layer of potential breakage. Nothing beats the native parser that comes with Hugo when it comes to convenience and compatibility.
  • Pure HTML output. This is a bit of a fallacy, though, considering the number of serverless Lambda functions I ended up having.
  • Knowing every corner of the frontend code and how it works. Don’t ask me to explain what the 152-c95759cd62a.js file you just downloaded does. Because I have no clue.
  • Total and complete portability. In exchange for a lot of convenience, I accepted a bit of vendor lock-in when I put all of my eggs in Vercel’s basket. Next.js is their creation, and other hosts (including Netlify, Render, DigitalOcean, etc.) have begun adding support for some of the core functionality. But quite a few of the more exciting features (like image optimization and analytics) are clearly designed for Vercel and Vercel only.

Doing the actual migration was pretty boring and uneventful (besides the wealth of React knowledge I got to teach myself, which I’m sure is still less than 2% of what’s out there). I found the goal with the least mental friction was to build a 1:1 replica of the Hugo site with Next.js, to the point where a visitor wouldn’t have been able to tell there was a change, except for much faster page transition times. You can track how that went in this pull request and judge for yourself...

As always, this entire site is open source, and I’ve also archived the Hugo version in a separate repository for reference and comparison.

Let me know what you think about the “new” site as well as frameworks like React and hybrid static/server tools like Next.js. To anyone else currently using Hugo or building pure HTML sites, have you been tempted recently by a JavaScript framework du-jour? Or are you ignoring the hype and waiting for the next trendy project to come along and “change everything” yet again? (Remix is my prediction, for what it’s worth...)