Cloudflare Rebuilds Next.js in a Week, Turbopuffer Queues in JSON, and Angular 20 Migration War Stories

Published on 25.02.2026

motyl.dev<div></div></>FRONTEND

How Cloudflare Rebuilt Next.js with AI in One Week

TLDR: A single Cloudflare engineer used AI to build vinext, a drop-in Next.js replacement built on Vite, in under a week for about $1,100 in tokens. It builds up to 4.4x faster and produces client bundles 57% smaller than Next.js 16.

Summary:

Alright folks, let me tell you about what might be the most audacious thing I have seen in frontend engineering this year. Cloudflare just dropped vinext, pronounced "vee-next," and it is a from-scratch reimplementation of the Next.js API surface built entirely on top of Vite. Not an adapter. Not a wrapper. A clean reimplementation. You run npm install vinext, swap your scripts, and your existing App Router, Pages Router, and next.config.js just work.

The numbers are striking. In benchmarks against Next.js 16 using a shared 33-route App Router application, vinext with Vite 8 and Rolldown builds production apps 4.4x faster, going from 7.38 seconds down to 1.67 seconds. Client bundles come out 57% smaller, shrinking from 168.9 KB gzipped to 72.9 KB. Now, they are upfront that this is a single benchmark on a specific app, so take it as directional rather than definitive. But the direction is very encouraging.

What makes this story fascinating is the development process. One engineer, technically an engineering manager, directed Claude through over 800 AI coding sessions across roughly a week. Almost every line of code was written by AI. But here is the part that matters: every line passes the same quality gates you would expect from human-written code. They have 1,700 Vitest tests, 380 Playwright end-to-end tests, full TypeScript type checking via tsgo, and linting via oxlint. The human still steered architecture decisions, prioritized work, and caught when the AI was heading down dead ends.

The article raises a genuinely provocative question about software abstraction layers. Most abstractions exist because humans need help managing complexity. AI does not have the same limitation. It can hold the full system in context and just write the code. Which of our frameworks and wrapper libraries are truly foundational, and which were just crutches for human cognition? That line is going to shift dramatically over the next few years.

What the article avoids confronting is the maintenance story. Building software in a week is one thing. Supporting it for years is another. Who debugs the AI-generated code when edge cases surface six months from now? They have 94% API coverage, which means 6% is missing. And vinext does not yet support static pre-rendering at build time, which is a deal-breaker for many production apps. They do introduce an interesting concept called Traffic-aware Pre-Rendering that uses Cloudflare analytics to only pre-render pages that actually get visited, but that is experimental and Cloudflare-specific. The honest assessment is that this is impressive engineering that proves a pattern, but calling it production-ready for anything beyond early adopters would be premature.

Key takeaways:

  • vinext is a drop-in replacement for Next.js built entirely on Vite, not an adapter or wrapper
  • Build times are up to 4.4x faster with Vite 8/Rolldown and bundles are 57% smaller
  • Built by one person directing AI across 800 sessions for about $1,100 in tokens
  • 94% Next.js 16 API surface coverage with extensive test suites
  • Experimental status with no static pre-rendering support yet
  • Deploys to Cloudflare Workers with a single command, with other platforms possible since 95% is pure Vite

How we rebuilt Next.js with AI in one week

How to Build a Distributed Queue in a Single JSON File on Object Storage

TLDR: Turbopuffer replaced their internal job queue with a system backed by a single JSON file on object storage, using compare-and-set writes, group commit batching, and a stateless broker to achieve FIFO execution, at-least-once delivery, and 10x lower tail latency.

Summary:

This is one of those engineering blog posts that makes you stop and reconsider your assumptions about infrastructure complexity. The team at turbopuffer, who run a search engine hosting over 2.5 trillion documents, rebuilt their indexing job queue using what amounts to a single JSON file on object storage. And they walk you through the entire thought process, building it up from the absolute simplest thing that could work.

The starting point is elegant in its simplicity. You have queue.json on object storage. A pusher reads the file, appends a job, and writes it back using compare-and-set, which is an atomic operation that only succeeds if the file has not changed since it was read. A worker does the same thing to claim a job. If the write fails because someone else modified the file first, you just read the new version and try again. For up to one request per second, this is already production grade because object storage handles durability, availability, and consistency for you.

But most queues need more than one request per second, so they add group commit batching. Instead of writing jobs one by one, they buffer incoming requests in memory while a write is in flight, then flush the entire buffer in the next compare-and-set write. This decouples write rate from request rate, shifting the bottleneck from write latency at about 200 milliseconds per write to network bandwidth at around 10 gigabytes per second.

The next problem is write contention. With hundreds of clients all trying to do compare-and-set writes on the same file, you get constant failures and retries. Their solution is a stateless broker that handles all interactions with object storage on behalf of every client. The broker runs a single group commit loop, so nobody contends for the object directly. And critically, it does not acknowledge a write until the data is durably committed to object storage.

For high availability, they add broker discovery through the queue file itself and heartbeat-based job failover. If a broker dies, clients start a new one and write its address to queue.json. If a worker dies mid-job, the heartbeat timeout causes the job to be reclaimed by another worker. The whole thing is simple, predictable, and built on primitives they deeply understand.

What I think is missing from the discussion is the failure modes at scale. What happens when the JSON file grows large enough that read-modify-write cycles become slow? They mention the total data is well under 1 GiB, which is fine for their use case, but this pattern does not generalize to queues with millions of pending jobs. They also do not discuss monitoring and observability in depth. When your entire queue state is a single file, debugging production issues requires different tooling than traditional queue systems provide.

Key takeaways:

  • A single JSON file with compare-and-set writes provides strong consistency without complex locking
  • Group commit batching decouples write throughput from object storage write latency
  • A stateless broker eliminates write contention and can serve hundreds of clients
  • Heartbeat-based failover handles both broker and worker failures gracefully
  • The pattern works because object storage provides durability, availability, and atomic writes as primitives
  • This approach trades generality for simplicity, working well when queue data is small enough to fit in memory

How to build a distributed queue in a single JSON file on object storage

Angular 20 Migration: SSR Challenges and Resolutions with Vite, ESM, and Express 5

TLDR: Halodoc shares a detailed account of migrating their Angular SSR monolith to Angular 20, covering 12 specific challenges across build tooling, module systems, server runtime, and Express routing, with production builds getting 56% faster in the process.

Summary:

If you are running Angular with server-side rendering in production, this is the blog post you want bookmarked before you attempt the Angular 20 upgrade. The team at Halodoc, which serves millions of users in Indonesia, documents twelve distinct challenges they encountered migrating their SSR monolith, and the level of detail here is genuinely useful.

The headline change is the shift from Webpack to Vite plus esbuild for the build system. Production builds dropped from about 8 minutes to 3.5 minutes, a 56% improvement. Development cold starts went from 45 seconds to 12 seconds, and hot module replacement now completes in under 200 milliseconds instead of 2 to 4 seconds. Those are real numbers from a real production application, not synthetic benchmarks.

But the build system change cascades into everything else. The output format switches from CommonJS to ESM, which means PM2 configs, Docker setups, and CI scripts all need updating to point to .mjs files instead of .js. The SSR engine changes from CommonEngine to AngularNodeAppEngine, which uses a Web-standard Request/Response model instead of the old render-to-string approach. And Angular 20's ESM-only SSR runtime forces you off Express 4, which is CommonJS-only, onto Express 5, which brings its own breaking changes to route syntax. The wildcard catch-all * no longer works. You need /{*path} for catch-all routes and :param(*) for wildcard parameter captures.

There are several subtle gotchas that would cost you hours if you did not know about them. Running ng update silently drops the SSR configuration blocks from angular.json. Builds appear green, but you are serving client-side rendered pages without realizing it, quietly degrading SEO. esbuild eagerly resolves all imports including conditional ones, so pdfjs-dist's optional import('canvas') for Node SSR helpers, which Webpack silently skipped, now crashes the build unless you externalize those modules. And Angular 20's stricter TypeScript checks mean catch blocks without typed error variables cause build failures.

What the article does not address is the testing strategy for verifying SSR output parity after migration. They mention testing route matching extensively in staging, but there is no discussion of how they verified that the rendered HTML from AngularNodeAppEngine matched what CommonEngine produced. For a site serving millions of users, that seems like a critical gap. The article also glosses over the monitoring story beyond the Prometheus singleton fix. How did they validate that the migration did not introduce regressions in real-user metrics like Time to First Byte or Largest Contentful Paint?

Key takeaways:

  • Angular 20 is an architectural shift, not just a version bump, touching build pipeline, server runtime, and deployment config simultaneously
  • Production builds improved by 56% moving from Webpack to Vite plus esbuild
  • The CommonJS to ESM transition affects PM2, Docker, CI scripts, and all server-side code
  • Express 5 migration requires rewriting all wildcard route patterns
  • Watch for ng update silently dropping SSR configuration blocks from angular.json
  • Add externalDependencies for native modules like canvas that esbuild resolves eagerly

Angular 20 Migration: SSR Challenges & Resolutions with Vite, ESM, and Express 5

Angular 21.1: Signal Forms Rename, Router Auto-Cleanup, and Template Syntax Extensions

TLDR: Angular 21.1 ships with a breaking rename in Signal Forms from Field to FormField, a new experimental router option for auto-destroying route-provided services, and template syntax additions including fall-through switch cases and the spread operator.

Summary:

Angular 21.1 is a smaller release, but there are a few changes that will affect your daily workflow if you are in the Angular ecosystem. The most impactful is a breaking rename in Signal Forms. The Field directive is now FormField, and [field] becomes [formField]. The old names are completely removed. Since Signal Forms are still experimental, breaking changes within minor and even patch releases are allowed, and they are exercising that right here.

One of the previously missing features of Signal Forms compared to classic reactive and template-driven forms was automatic CSS classes like ng-valid, ng-dirty, and ng-touched. This is now possible using the provideSignalFormsConfig() function. That is a meaningful step toward Signal Forms being a viable replacement for the older form APIs.

The new experimental router option withExperimentalAutoCleanupInjectors() addresses a long-standing awkwardness with route-provided services. Previously, if you provided a service at the route level, it would get instantiated when users entered that route but would never get destroyed, even when they navigated away. It also was not available in other routes. So it was this odd middle ground between a global singleton and a component-scoped service. The new option makes these services behave like component-provided ones, getting destroyed when users navigate to a different route. If you have been using takeUntilDestroyed() in route services, this feature is exactly what you need.

Template syntax gets two additions. You can now write multiple consecutive switch case statements that fall through to the same block, similar to JavaScript's fall-through behavior. And the spread and rest operator with three dots is now supported in templates. Neither is revolutionary, but both remove paper cuts that Angular developers have been working around.

What is not discussed much is the upgrade path for teams already using Signal Forms in production. The Field to FormField rename is a straightforward find-and-replace, but it highlights the risk of building on experimental APIs. If you adopted Signal Forms early, you are now maintaining code that breaks on patch releases. The article could benefit from guidance on how to insulate your codebase from these kinds of churn when using experimental features.

Key takeaways:

  • Signal Forms breaking change: Field renamed to FormField, [field] to [formField], old names removed entirely
  • New provideSignalFormsConfig() enables automatic CSS classes for Signal Forms
  • Experimental withExperimentalAutoCleanupInjectors() auto-destroys route-provided services on navigation
  • Template syntax now supports fall-through switch cases and the spread/rest operator
  • Angular 21.2 is planned for the week of February 23, with Angular 22 targeted for May

Ng-News: Angular 21.1

Never Run claude /init: The Case for Handcrafted CLAUDE.md Files

TLDR: Running claude /init generates a CLAUDE.md file that bloats your system prompt with auto-discovered documentation, wastes tokens on every request, and quickly goes out of date. The better approach is writing your CLAUDE.md by hand or heavily editing what init generates.

Summary:

This is a debate that has been heating up in the AI-assisted development community, and it touches on something fundamental about how we work with these tools. The argument, popularized by Matt Pocock, is straightforward: running claude /init auto-discovers your codebase and generates a CLAUDE.md file packed with documentation. That file then gets loaded into the system prompt on every single conversation. Every token in that file competes for attention with the actual work you are asking Claude to do.

The core insight is about context management. CLAUDE.md is loaded before every conversation. If it is full of obvious things that Claude already knows from its training data, or boilerplate that does not actually influence the quality of its output, you are burning tokens and diluting the signal. A framework name, a list of directories that follow standard conventions, basic TypeScript configuration, none of that needs to be spelled out. Claude already knows what a Next.js app structure looks like.

The counterpoint, articulated well in the Builder.io guide on writing good CLAUDE.md files, is more nuanced. The recommendation is not to skip init entirely but to use it as a starting point and then aggressively edit. Deleting is easier than creating from scratch. The generated file gives you a skeleton, and you remove everything that does not earn its place. The practical advice is to keep it under 300 lines and make sure every line is specific and actionable. "Format code properly" is useless. "Use named exports, not default exports" is useful.

What I find interesting is the organizational structure that has evolved around this. You can place CLAUDE.md files in subdirectories for module-specific instructions, use CLAUDE.local.md for personal preferences that should not be version controlled, import other files with the @ syntax to keep the main file lean, or split rules into a .claude/rules/ directory for team ownership. This is starting to look like a configuration management system, which tells you something about how seriously people are taking AI-assisted development workflows.

The missing piece in this entire discussion is measurement. Nobody is showing data on how different CLAUDE.md configurations affect output quality. We are all going on vibes and anecdotal experience. Does a 50-line CLAUDE.md actually produce better results than a 300-line one? Does including your architecture diagram help or hurt? Without controlled experiments, this is all cargo-culting. What we need is someone to systematically test different configurations against a consistent set of coding tasks and measure success rates.

Key takeaways:

  • The auto-generated CLAUDE.md from /init often includes redundant information that wastes context tokens
  • Use /init as a starting point, then aggressively remove anything that does not influence output quality
  • Keep CLAUDE.md under 300 lines with specific, actionable instructions
  • Use subdirectory CLAUDE.md files, @imports, and .claude/rules/ for larger projects
  • CLAUDE.local.md can hold personal preferences outside version control
  • The real gap in this discussion is empirical measurement of how CLAUDE.md content affects output quality

How to Write a Good CLAUDE.md File