Software Engineer — Shelter Island, NY

Barnabas
Kendall

I design and build web applications, then write about the process — and about whatever else catches my attention.

> currently rebuilding this site_
Barnabas Kendall

The Blog

page 1 of 4
InfraWeb Platform

Sharp Edges

As a little follow-up/reality check to my previous post, I’d like to document my frustration with configuring and launching a new Astro site on Cloudflare, which feels ironic. The specific combo I’ve been wrestling with is pnpm v111 and Astro v7 on Cloudflare Workers.

Try this: in an empty directory, run pnpm init to get a basic package.json file, then follow the manual setup in the Astro docs: run pnpm add astro, add a page at src/pages/index.astro, and add dev/build/preview scripts as documented. Now you should be able to run pnpm run build and get some HTML in your /dist folder. Hooray! But now let’s put it on Cloudflare. If you want it connected to your source control, initialize git and push your folder to GitHub or GitLab.2 OK, almost there!

When you connect your new Workers project to your new repo, you’ll briefly feel a sense of hope. Did I really just deploy my new Astro site in three clicks? Ha, no. You’ll go through several failed builds before you get a success. In no particular order, here’s what it actually took:

  1. Set the PNPM_VERSION environment variable to 11, because out of the box right now the build system uses v10, and that won’t work
  2. Delete the packageManager field from the default package.json that pnpm init creates — it’s invalid and will fail the build
  3. Add wrangler as a dependency; without it in package.json, pnpm won’t build the project at all
  4. Run pnpm approve-builds so pnpm is allowed to run the install scripts for workerd and esbuild
  5. Maybe you do need a wrangler.jsonc after all? Or should you run astro add cloudflare?
  6. etc. etc. etc.

The actual steps needed to make the build work took some trial and error, which is frustrating because all indications are that it should be automatic. Make change, push to origin, check build logs for fail, repeat. I don’t envy the engineers tasked with making that work — there’s no doubt a huge backlog of bugs from esoteric combinations of things that don’t behave as they should.

That said, I don’t think it’s unreasonable to expect that in Q3 2026, pnpm, Astro, and Cloudflare should Just Work.

Footnotes

  1. According to State of JS 2025, pnpm was the most-used monorepo/package-manager tool among respondents to that question, and v11 was released in April. I’m just saying it’s not an obscure or bleeding edge tool.

  2. Of course you’ll need a .gitignore file too so you don’t commit node_modules, the /.astro folder, or anything else accidentally, but maybe this is too obvious to mention…

InfraWeb Platform

Cloudflare Pages vs. Workers

I’ve recommended Cloudflare Pages in my Budget Website guide for years as the free, git-connected way to host a static site. Lately I’ve been wondering if that’s still the right call, so I went digging through the docs.

Pages launched in December 2020 as “the best way to build JAMstack websites,” pitched squarely at Netlify and Vercel, not really at GitHub Pages or GitLab Pages. But it landed in the same free-static-hosting bucket as those two anyway, which is exactly why it sits alongside them as a runner-up in my own guide. Similar name, overlapping job, different original target.

What’s changed since 2020 is Workers. In September 2024, Cloudflare shipped native static asset serving for Workers, replacing the old, awkward Workers Sites. That closed the one real gap that used to force a choice: static-with-git-deploys meant Pages, anything dynamic meant Workers. Cloudflare’s own words on this are unambiguous: “Now that Workers supports both serving static assets and server-side rendering, you should start with Workers.” Pages isn’t deprecated — existing projects keep running fine — but “all of our investment, optimizations, and feature work will be dedicated to improving Workers” going forward. Read: maintenance mode.

The gap keeps widening in Workers’ favor. Cron Triggers don’t exist on Pages. Durable Objects need a separate Worker bolted on rather than native support. Observability is thinner — no Workers Logs, Logpush, Tail Workers, or source maps. Email Workers, Rate Limiting, and Queue consumers are Workers-only. Since new platform features land on Workers first, this list only grows. Pages still has a few things going for it — early hints on static assets by default, file-based routing without a framework, Pages Plugins, more configurable branch deploys — but I wouldn’t bet on those staying differentiators for long.

The part that actually matters for my guide: “Static Hosting” and “Dynamic Hosting” used to be a genuine fork in the road, and I wrote Budget Website with that fork in mind — Pages if you’re static, Fly.io or Render if you need a real backend. Workers dissolves that fork. You can start static and, if the project later grows a database or an API, add a fetch handler and a binding instead of re-platforming. That’s a better long-term position than Pages, and arguably better than Netlify or Vercel’s split models too, since it’s the same runtime and the same deploy either way. I’m updating my pick accordingly.

AIIdeas

Is It Cheating?

There’s a lot of hand-wringing on the Internet about engineers using LLMs to code: whether we’re doing ourselves a disservice, letting our skills atrophy, getting dumber, shooting ourselves in the foot, digging our own graves, etc. My own experience working alongside AI tools like Claude Code has reignited my imposter syndrome to a degree.

I’ve felt this way before. Years ago I was asked to interpret a half-hour speech from English into Japanese. I was given the outline the speaker would use ahead of time, and I knew my Japanese was just barely good enough to do an adequate job with preparation and my own notes on the tricky vocabulary. But I wanted better for the audience, so I “cheated.”

I got lucky: the speaker gave the same speech in two other cities in the prior weeks, each time interpreted live by a different native Japanese speaker. I attended both and made recordings. My heart sank as I listened. Their fluency and speed were on another level, and I realized I was in way over my head. The speaker kept wandering off his outline: jokes, metaphors, asides, and both interpreters rolled with it effortlessly. I transcribed their work and pored over it, looking for hints. But the closer I looked, the more the cracks showed. For all their polish, neither was perfect. One would land a more accurate or evocative phrase where the other fumbled. Sometimes they’d quietly ask the speaker to repeat or rephrase a line; other times they’d quietly drop a minor idea, or reach for a complicated phrase when a simple one would have done.

Interpreting from English

Armed with their prior performances, I ended up doing a much better job than I would have on my own, so much better that I felt a little embarrassed. Afterward, when people congratulated me on my sudden interpreting talent, I admitted to “cheating” and gave credit to the other two interpreters. My bilingual friend Yuji shrugged this off as simply good preparation. He pointed out I’d also had an advantage neither interpreter had: as a native English speaker, I had 100% comprehension of the source material. During that talk he himself was thrown by a mangled English idiom, mishearing “two peas in a pod” as something like “two bees in a pod,” and losing a sentence chasing the wrong image.

So was I cheating? If the point was to test my personal, unaided ability to interpret English into Japanese, then yes. Or if I’d basked in the praise and let people believe I was naturally that gifted, also yes. But the actual point was never me. The focus was on the speaker and what he had to say, not on my performance. The goal was audience understanding, not Barnabas glory. The ends justified the means, because the means were never really the point.

Writing software with AI coding agents, I feel a similar twinge of guilt. I labor over a prompt, turn a state-of-the-art agent loose on a giant codebase, and it comes back with sharp analysis and working code in a fraction of the time it would have taken me. It makes me feel old and slow. But on closer inspection, the polish has cracks too: my counterpart sometimes misses the “metaphor” (the right abstraction), over-engineers something that should be simple, or quietly drops a requirement altogether. Nobody seriously expects engineers to hand-type all their code anymore, so while my raw “output” stopped being a measure of my ability, that was never really the point either. The real question is whether I, with all my tools and preparation and experience, can still produce tasteful craftsmanship. That has always been the only task worth doing.

Writing

Just One Bite

A short parable about trust, taste, and the difference between a job and a craft.

There was once a small ship that sailed a steady, well-known coast. Aboard her were a captain, a first mate, and a cook.

The captain was a careful man. Each evening he sent for his plate and studied it like a chart: not enough salt here, too much butter there, cut the potatoes smaller next time. He had eaten the same handful of dishes for ten years and saw no reason a hundred more wouldn’t do just as well.

The first mate carried these instructions below deck, word for word, and brought them back the same way. He had served under the captain a long time and took quiet pride in knowing the man’s tastes before the man had to state them.

The cook did his best with what he was handed. He’d spent thirty years in kitchens before this ship, and most nights he could tell the dish being requested wasn’t really what the captain wanted, only what the captain knew how to ask for. But a cook cooks, so he cooked.

One evening he made something of his own, using spices from his personal collection. Nothing strange — no dish the captain couldn’t recognize the parts of, just put together with more care than usual, and a taste he was certain the man would like, if only he tried it.

The cook tries

He brought it to the first mate. “Take this up to him. Just one bite. He doesn’t have to keep it.”

The mate shook his head. “I know him. He doesn’t like surprises on his plate, and he likes even less finding out money is being wasted on ingredients he won’t eat. If he hates it, that’s on me.”

“He might love it.”

“He might. But I’m not going to be the one who finds out.” The mate paused, then added, “Also, I tasted it myself, in the galley. I’d have gone lighter on the thyme.”

“Then come taste it with him,” the cook said. “Let him decide, with both of us in the room.”

There was no room to be had, the mate said. Not this season.

So the dish was thrown overboard uneaten, and the cook returned to smaller potatoes and less seasoning. What stayed with him wasn’t the refusal — men refuse things — but the shrug underneath it, the sense that no one aboard found it strange for a man to eat the same bland dishes for ten years running and call it a decision rather than a habit he’d never once questioned. It was his ship, his plate, his right. The cook didn’t dispute that. He just found it a small and joyless way to eat.

At the next port, the cook came up from below and said he’d be leaving.

They were baffled. By their measure he’d done the job well — the plates came out, the crew ate, no one had gone hungry. “We don’t understand,” the mate said. “You’re good at this.”

“I know,” the cook said. “But you don’t want a cook. You want someone to combine what’s already been chosen, the way it’s always been combined. That’s honest work, and someone should do it. I just spent thirty years learning to be a chef — and a chef needs a kitchen where the only person who has to be talked into a new dish is the one eating it.”

He picked up his knives and went to go find a restaurant of his own.

The cook leaves

The Grug Brained Developer is a plea for humility over cleverness, dressed up as a caveman’s diary.

kottke.org is Jason Kottke’s long-running “liberal arts” blog, and it’s the direct inspiration for these short link posts — full posts below are still mine, but the little in-between notes are a Kottke move.

MapLibre GL JS is the open-source fork that kept vector-tile mapping alive after Mapbox GL JS went proprietary — worth a look if you, like me, have spent time elbow-deep in PostGIS.

VueSide Project

Cheaty Flashcards

My niece just finished the fourth grade and we were commiserating about learning multiplication tables. We thought, what if there was a game for learning math that let you cheat just a little bit so you don’t get too discouraged? Soon after, Cheaty Flashcards was born.

Cheaty Flashcards gameplay

Some of the tech choices I made are as follows. I reached for my favorite Vue + Vite combo and used the TypeScript starter. I styled it with Tailwind CSS and daisyUI, a great combination of sensible boilerplate and flexibility. Other dependencies include Unhead for dynamic page titles, Fontsource for the web font Fredoka, and unplugin-icons to embed Feather SVGs as components. The new-to-me library this time was howler.js for playing sound effects.

I pushed the app source code to a private GitHub repository. I’m using Cloudflare Pages as the host and tracking usage with Beam Analytics. The whole thing took only a few hours between idea and v1 on the web.

Some may see the above and bemoan the state of frontend development. “What ever happened to FTPing some static files to a shared web server?” they cry, yelling at the cloud. I’m tempted to agree, especially for a little free toy app. But, tooling like this brings the ability to build complexity and make changes rapidly. And personal projects like these are to me what practicing scales are to a musician.

Astro

Starlight

This month I had the opportunity to take Starlight for test drive as a demo for my team’s internal documentation. Although Starlight currently comes with a warning that it’s in early development, we were impressed by its polish and features. I think it has real potential to challenge established documentation SSGs like VitePress or Docusaurus.

If you already have Markdown files, it is absurdly easy to publish them with Starlight. Those are basic table stakes for any SSG, though. If you need to do something unusual in your docs site, such as host an API or enforce authentication, good news! Starlight is built on Astro, so you have the full power of Astro and its integrations at your disposal. For our demo, I enabled diagrams with the astro-diagram plugin. Like any Astro site, you can create components in Astro’s syntax or any supported framework including React, Preact, Svelte, Vue, SolidJS, AlpineJS and Lit.

Documentation is not worth much if the content is not discoverable. You can configure the sidebar manually or just point a sidebar section at a directory, which is nice. But Starlight’s pièce de résistance IMO is the excellent static site search courtesy of Pagefind, and I’m already a fan. Other solutions in this space don’t have a built-in search, have a very minimal search UI, or rely on an external service like Algolia.

The drawback of Starlight, aside from its wet-behind-the-ears newness, is the inflexibility. There’s no way to make a custom page that uses the Starlight’s layout, as of today. You can only have one sidebar, so this might make certain kinds of documentation difficult or impossible. VitePress supports multiple sidebars if that’s a deal breaker. It is certainly difficult to balance simplicity with customizability.

For our use case, we ultimately decided against using Starlight in favor of GitHub’s wiki feature. It might look comparatively boring and have a terrible search UX, but GitHub’s wiki edit and publish workflow is good enough for internal documentation. Besides, access is already restricted to the team members, something we’d have to replicate somehow with Starlight.

MiniSearch is the small, fast full-text search library quietly powering this site’s search.