Zack Proser

Maintaining this site no longer fucking sucks

Ten days ago, I wrote about how maintaining the last generation of my personal portfolio site absolutely FUCKING SUCKED, and how I knowingly did things the painful way on purpose for years.

And yet, here I am exactly ten days later, writing that maintaining this site no longer fucking sucks. What has changed in so short a time between that post and now?


Maintaining this site no longer fucking sucks

I tried Vercel, and the latest Next.js

In this post, I'll share what I learned during my most recent total site re-write. I may have just fallen in love.

The latest stack

  • Next.js 13.3
  • Tailwind UI / CSS
  • Vercel for framework defined infrastructure

Even as a grumpy developer, I was pleasantly surprised. This stuff is fast

Why? Error reporting is light-years better than I ever remember it being for frontend development.

Next.js errors are pleasant to work with and human-legible

The errors were human legible and I experienced very few WTF moments throughout a weekend of furious hacking. This is some of the highest ratio of time spent just building features and shipping content versus wrestling with my tooling and debugging why something simple isn't working.

The tooling is fast now. The hot reloading works more smoothly. I can now run npm run dev once and hack away. The bundle compiling is noticeably quicker.

Things that used to be tedious are not now

It feels like I stepped away for a couple years and then many of the UX pain points for developing on the frontend were handily resolved. To give some concrete examples, the way next.js handles loading Google fonts.

In the far distant past, a couple of years ago, loading a Google Font on your site used to involve a bunch of manual tedium. You had to peruse the Google fonts site, figure out what looked okay, download the fonts, unzip them, install them properly, realize you didn't install them properly and try again, and, of course, you had to spend the requisite 2 hours on stack overflow figuring out why none of them would load.

Here's how you use Google Fonts now in a next.js project, by writing out its name in a component:

// pages/_app.js
import { Inter } from 'next/font/google'

// If loading a variable font, you don't need to specify the font weight
const inter = Inter({ subsets: ['latin'] })

export default function MyApp({ Component, pageProps }) {
  return (
    <main className={inter.className}>
      <Component {...pageProps} />
    </main>
  )
}

More importantly, the optimization happens server-side - and a request for the font is never sent to Google when someone hits your site. This is fantastic. The other thing I forgot to mention about using Google Fonts in the past is that you also had to do it incorrectly so that you could slow your page way down. With next.js, it's now painless to create a branch with a font experiment. Painless, I tell you! You had to be there in the past back when it was bad to understand why this is amazing.

It took me three minutes to throw up a branch with a pull request to review what it would look like to change my site back to the custom Google Font Oxygen I've used in the past.

I was able to pop open two firefox tiled windows, one with production and one with my font experiment, and eyeball the two fonts together side by side and decide I wanted to stick with the default.

Again, the speed of iteration is profound. It enables new ways of working. In the past you had to know me personally to be able to file a ticket to ask me to screw with the fonts on the site. You had to bring oblations and say you were sorry. Now you can preview whatever font you want, buddy!

Enhanced Markdown or MDX works intuitively

Most of the things I was doing via libraries and custom code in the previous generation of my site to get the markdown authoring experience I wanted, are now first-class features of MDX / next.js.

This includes the use of the prism library for code syntax highlighting which even supports custom themes (the one you're seeing on my code snippets is gruvbox and it's the same one I use for in my Neovim setup).

So far, the MDX integration has been exactly how I wanted to work with my blog content, because markdown is easy to maintain and quick to write, but I can now also render rich graphs or media to enrich my content.

Here's a quick snippet to show what I mean. For the most part, it's still just markdown, but now you can build and import rich graphs and diagrams, images, gifs (which I use for terminal demos on my site), and much more:

import { ArticleLayout } from '@/components/ArticleLayout'
import Image from 'next/image'
import HeroImage from './symfony-optimizer-splash.webp'
import ExampleReport from './article-optimizer-example-report.webp'

export const meta = {
  author: 'Zachary Proser',
  date: '2022-07-14',
  title: 'A powerful and open source content optimizer',
  description: 'A full technical deep-dive on my optimizer app and its features',
  image: 'optimizer-blog.webp'
}

export default (props) => <ArticleLayout metadata={metadata} {...props} />

<Image src={HeroImage} />

## Overview

This post will walk through my open-sourced Article Optimizer app...

I can think of a number of projects over the past years this would have significantly simplified. Years, ago, we tediously composed rich-media "threat reports" as giant web pages that required constant collaboration with multiple departments. This stack is ideal for such contexts.

I had the majority of my site's content migrated from my previous site to my Tailwind UI template site in a single weekend. The integration with tailwind was seamless and the integration with Vercel was seamless.

Once I had tweaked the template to my needs, I was mostly moving markdown in place, rewriting image links and adding some MDX / react preamble to my markdown files, which otherwise did not need to change. This was one of the least painful migrations I've done. Full stop.

React is pretty fast, as is - everything else...

The git integration with Vercel is excellent just as it is with Netlify. It allows for very rapid iterate, preview, deploy cycles. I built a working landing page with a form component (meaning there's a backend dependency as well!) by composing some components I had just modified and gotten working, deployed it to a preview environment, verified it there, and promoted it to production within 13 minutes - while having a conversation with someone.

This is a testament to this tooling and all the work that has gone into making the developer experience excellent. At long last, I may also have settled on a slight personal preference for React. Something about it clicked enough where it felt natural to create, and easy to reuse, my UI components.

The backend API handling is really nice once you grok it

Again, speaking from the perspective of speed of iteration and rapid deployment, the next.js API integration / backend function generation is actually really nice. Imagine you have a form that needs to capture emails from your blog subscribers and send it on to a third party service to subscribe the new reader to your list.

Next.js uses the concept of Pages, which are React components exported from [.js, .jsx, .ts, tsx] files. For example, if you create pages/about.js, then that React component will be available at /about. This allows for very rapid iteration once you understand this, and you can easily re-use components across pages to keep your code dry but still build very quickly.

But where do you put the form handling backend code? And how can you securely process values in your backend form while still open-sourcing your entire site?

For API handling, Next.js allows you to add code to pages/api/<blah.js> . For example, the following code, written in pages/api/user.js would return a JSON response representing the user when you git /api/user

export default function handler(req, res) {
  res.status(200).json({ name: 'John Doe' })
}

Any code you write here needs to satisfy the interface demonstrated above, of a function accepting a request and a response object.

So what does Next.js / Vercel do with code you write in pages/api/? it converts it to a lambda essentially, or a Vercel function, which you can easily view the logs for in the Vercel dashboard or via the CLI.

And when you develop locally? Then next.js just runs your function locally so you can see console.log output in the same terminal where you ran npm run dev.

All this means that you can write frontend and backend code in an organized way in a single repository, have an outstanding experience running it locally and in production, and still handle secrets via environment variables so that you never check anything sensitive into version control.

Here's the form processing code for my simple email subscribe form:

export default async function handler(req, res) {
  // Get data submitted in request's body.
  const body = req.body

  // if email is missing, return an error 
  if (body.email == "") {
    res.status(400).json({ data: `Error: no valid email found in request`})
  }
  // Read the secrets (API Key and List ID) out of Vercel environment variables
  const emailOctopusAPIKey = process.env.EMAIL_OCTOPUS_API_KEY
  const emailOctopusListId = process.env.EMAIL_OCTOPUS_LIST_ID
  const newMemberEmailAddress = body.email
  const emailOctopusAPIEndpoint = `https://emailoctopus.com/api/1.6/lists/${emailOctopusListId}/contacts`

  const data = {
    api_key: emailOctopusAPIKey, 
    email_address: newMemberEmailAddress,
    status: "SUBSCRIBED"
  } 

  const requestOptions = {
    crossDomain: true, 
    method: 'POST', 
    headers: {'Content-type':'application/json'}, 
    body: JSON.stringify(data)
  }

  const response = await fetch(emailOctopusAPIEndpoint, requestOptions)
    .then((response) => response.json())
    .then((data) => console.dir(data))
    .catch((error) => console.dir(error))

  // Sends a HTTP success code
  res.status(200).json({ data: `Think we successfully subscribed ${body.email}` })
}

Within the Vercel dashboard, without doing any additional work whatsoever, you now get first class logging for every request to your functions:

Logging is first-class for Vercel serverless functions

The vercel integration is scary good

I mean that literally. I'm slightly unnerved by how smooth of an experience deploying to Vercel was, because now I find myself wanting to launch tons of sites and projects there - which I'm certain is exactly their business model. But I am also impressed. As a first time user, I will also be a return user, and the cognitive barrier to entry for launching my next projects just went way, way down.

What does this all mean together? Next.js is fast - stupidly fast - to work with

When you combine all the previous points you can start to see how you could rapidly build and easily maintain many high quality and performant sites at the same time. The iteration loop with this stack is insanely fast, and so much work has been put into the framework and tools when I'm getting these google lighthouse scores out of the box for desktop and mobile. And subjectively, loading my site on my desktop or phone feels really fast. I'm itching now to fire up the next project...