RSSAmplifier

Blog

nickb.dev

Recent content on nickb.dev

nickb.devRSS feed ↗151 posts

Latest posts

There's always a niche: rawzip and the cost of ergonomics

I recently released rawzip 0.5, a zip archive file parser that focuses on minimalism in terms of dependencies (there are none) and overhead. 
 Looking at benchmarks: rawzip has anywhere from 15x to 15000x less overhead than the next fastest zip library. 
 
 I’m using the overhead terminology deliberately. Most use cases should see compression dominate profiles. Most . 
…

When React strict mode meets a one-way door

This React code will cause a DOM error to be thrown: &#xA; function transferToOffscreen ( node : HTMLCanvasElement | null ) {&#xA; node ? . transferControlToOffscreen ();&#xA; }&#xA; &#xA; export default () => < canvas ref = { transferToOffscreen } />;&#xA; The error: &#xA; &#xA; Uncaught InvalidStateError: Failed to execute &rsquo;transferControlToOffscreen&rsquo; on…

Wasm is not an implementation detail

Over 5 years ago I wrote about overhauling a JS parsing library by rewriting it in Rust and compiling to Wasm. 1 I smugly implied that this would go unnoticed by users: &#xA; &#xA; By inlining as base64 we make the Wasm an implementation detail &#xA; &#xA; This was the wrong mentality 5 years ago, and it&rsquo;s the wrong mentality today. It took me a couple years to fix the recommendation 2 , but…

The serde optimization gauntlet: Wasm and arenas

Arena allocators are having a moment in the Rust ecosystem. They&rsquo;re powering the next generation of JavaScript tooling 1 , showing up in Reddit success stories 2 , and promising three key benefits: &#xA; &#xA; Amortized allocations &#xA; Cache locality &#xA; Efficient drops &#xA; &#xA; I&rsquo;m working on a project that deserializes 200MB of binary data in the browser via Wasm, where…

Life after wasm-pack: an opinionated deconstruction

Wasm-pack, the rustwasm working group, and other Wasm related tools were sunset and archived in July 2025 1 , after more than 5 years of being on life support. Thank you to all who made Wasm and Rust what it is today: a tech stack that continues to enthrall me. &#xA; Despite the years of ongoing maintenance issues, I and others continued to use wasm-pack as it&rsquo;s recommended by the…

Top-level await is a footgun: The Wasm worker edition

Web workers that load Wasm through an ES6 import with the help of bundler plugins can silently drop messages during startup. This happens because the top level await for Wasm&rsquo;s asynchronous initialization blocks the worker&rsquo;s message handler registration, creating a race condition that&rsquo;s often invisible. &#xA; As an example, the web worker module below will drop messages until…

Are SIMD gather instructions underrated?

A light-bulb went off over my head when I wrote a loop that contained repetitive lookups with no conditional logic: &#xA; // pseudocode&#xA; let data = [ 0 u32 ; 1024 ];&#xA; let indices = [ 0 u16 ; 1024 * 1024 ];&#xA; let output = [ 0 u32 ; 1024 * 1024 ];&#xA; for i in 0 .. output.len() {&#xA; output[i] = data[indices[i]];&#xA; }&#xA; That for-loop is the poster child for AVX2 gather instructions…

The DuckDB and Arrow rabbit hole: data modeling adventures

I didn&rsquo;t understand the hype around Duckdb, until I was approached to query a set of proprietary-yet-close-to-json files I have totalling 300 GB uncompressed, 25 GB compressed. &#xA; For prior requests, I wrote a bespoke CLI program to process the files and spit out the statistics. It was finally time for the task: package the data in a format that allows one to make arbitrary queries. &#xA;…

A Bevy app entirely off the main thread

There are several options for how to structure a Bevy app for the web: &#xA; &#xA; Run the Bevy app and canvas on the main thread &#xA; Run the Bevy app on the main thread but with an OffscreenCanvas &#xA; Run a Rust &ldquo;window&rdquo; on the main thread with the Bevy app and OffscreenCanvas in a web worker &#xA; Remove Rust from the main thread and run everything in a web worker &#xA; &#xA; As…

Wasm&#39;s curious word size and the SWAR advantage

Oftentimes the size of addressable space is the same size as the system architecture&rsquo;s preferred unit of operation, also known as its word size . On the vast majority of consumer hardware and servers (both x86 and Arm), these will both be 64 bits. &#xA; However this is not the case with Webassembly (Wasm), which has 32 bits of addressable space 1 but a word size of 64 bits. &#xA; But wait,…

Default musl allocator considered harmful (to performance)

TLDR : In a real world benchmark, the default musl allocator caused a 7x slowdown compared to other allocators. I recommend all Rust projects immediately add the following lines to their application&rsquo;s main.rs : &#xA; // Avoid musl's default allocator due to lackluster performance&#xA; // https://nickb.dev/blog/default-musl-allocator-considered-harmful-to-performance&#xA; #[cfg(target_env =…

The right tool for the job: positioned IO for Zip archives

pread is a little known POSIX API that allows one to issue file reads at an arbitrary offset in a thread-safe fashion, as it won&rsquo;t mutate the file&rsquo;s underlying cursor. &#xA; The Linux man-pages give us insight into pread&rsquo;s usefulness along with its sibling, pwrite . &#xA; &#xA; The pread() and pwrite() system calls are especially useful in multithreaded applications. They allow…

Keeping up with the fronted Joneses: streaming server side rendering

React server components. Streaming server side rendering. Edge compute. Web app architecture advancements are making classic SPAs appear long in the tooth. &#xA; Time to see what I&rsquo;ve been missing, and try to keep up with the Joneses (negative connotation fully implied). &#xA; I might suffer from Perpetual Reengineering Syndrome as this is the umpteenth time a 100k LOC web app side project…

Cloudflare&#39;s forbidden Steam

I&rsquo;m moving more code into Cloudflare workers everyday as they are cheap, fast, and powerful. But it hasn&rsquo;t been without struggle. &#xA; This is the story of one such struggle with a troublesome endpoint. &#xA; The endpoint in question verifies the OpenID authentication signature directly with Steam. In short, this code runs after the user authenticates with Steam, and when we receive a…

The hidden nuance of the JavaScript File API

Did you know that the files originating from an file input don&rsquo;t have all their data buffered into memory? Seems intuitive that this is how JS would work, otherwise web sites operating over files would be terribly memory inefficient. &#xA; Don&rsquo;t let this efficient File deceive you. If you create one yourself, you&rsquo;ll soon find that you need to buffer all data into memory. &#xA; To…

SQL reduction: ask for forgiveness

In an app that has S3 files and a Postgres database storing file metadata, we might have a DELETE route that looks something like: &#xA; &#xA; Select file from db, and if it does not exist throw a 404. &#xA; If the file&rsquo;s owner isn&rsquo;t our session&rsquo;s user, check the db if the session&rsquo;s user is an admin &#xA; Delete file from the db &#xA; Delete file from S3 &#xA; &#xA; The…

Next.js on Cloudflare: a gem with rough edges

I came across some high usage under my Vercel account. &#xA; &#xA; &#xA; &#xA; &#xA; &#xA; Vercel usage donut charts. Cropped for brevity &#xA; &#xA; &#xA;&#xA; What is &ldquo;Fast Origin Transfer&rdquo;? The docs are vague , and whatever it is, seems like I&rsquo;m about at my limit. &#xA; Vercel used to have straightforward pricing, but they recently updated their model . &#xA; &#xA; Instead of…

The accidental journey to TrueNAS Scale

Last month I found myself in the unenviable position of having just executed the most dangerous command ( dd ) on the boot drive of the NAS of my homelab. I was trying to wipe stale ZFS partition information from a data drive after it became briefly detached, but on reboot, the drive labels had been reassigned and I didn&rsquo;t double check if I was wiping the correct drive. &#xA; I caught my…

Dot or Not? A type safety story about file extensions

Let&rsquo;s write a function that takes in a file extension and returns a file type: &#xA; function fileType ( fileExtension : string ) {&#xA; switch ( fileExtension ) {&#xA; case '.mp4' : return 'video' ;&#xA; // ...&#xA; }&#xA; }&#xA; Our function assumes the file extension has a leading dot, but should it? &#xA; &#xA; The Python standard library includes the dot in file extensions . &#xA; Rust…

Practical responsive image sprites for the web

An image sprite sheet is a collection of images combined into one, so that browsers don&rsquo;t need to request each image separately. &#xA; How does one create a sprite sheet? &#xA; And how can we measure any potential benefit? &#xA; What problems can appear and how do we work around them? &#xA; &#xA; This post features screenshots from pdx.tools . Game assets shown are from EU4 and are for…

Pitfalls of React Query

Tanstack Query , (née React query for the purposes of this post) is a staple in async state and data fetching management. In addition to saving me from reimplementing similar caching mechanisms and fine grained updates, it was also the first library that solidified the concept of client vs server state, and how they are fundamentally different . Nothing is without tradeoffs, so I wanted to shed…

You might not need a React animation library: transitions

While React animation libraries have seductive landing pages and can be powerful, consider whether you truly need one. These libraries can enhance user experience, but sometimes the simplest solution is the best one. &#xA; Many animation needs are met with just CSS, so come along for a tour at ways one can add effects to their web application using this framework agnostic building block. &#xA;…

The composition king: compound components

Repeatedly adding orthogonal properties to a React component causes unwieldy bloat. Transitioning to a compound component will invert control and allow clients the flexibility they need. It comes at a cost, though. What are these costs, can we mitigate them, and what are alternative solutions? &#xA; Let&rsquo;s build a motivating example of a component&rsquo;s growth. Say we&rsquo;re building an…

The WebAssembly value proposition is write once, not performance

In his 2024 programming predictions video, Theo downplayed WebAssembly (Wasm) : &#xA; &#xA; I don&rsquo;t think [Wasm is] going anywhere next year. [&hellip;] &#xA; When I see all of these new ways of building Rust based web applications, I cringe a tiny bit [&hellip;], the size of the binaries are still pretty bad, the performance wins aren&rsquo;t there, and the flexibility of a language like…

The dark side of inlining and monomorphization

I felt clever. After spending a week developing an incremental lexer that would allow pdx.tools to deserialize decompressed saves at a constant memory usage, I was excited to integrate it. I had worked very hard to craft a serde deserializer that was optimized to pull from a lending, incremental lexer without the performance caveats seen in serde_json . &#xA; Writing a serde deserializer over a…

Split Next.js across hosting providers and advocate for direct S3 uploads

I was migrating a Next.js application ( pdx.tools ) from a self hosted instance to run serverless and edge functions over on Vercel . &#xA; One serverless endpoint accepts an uploaded EU4 save file, parses it via a microservice , uploads to S3, and sticks the parsed data in Postgres. Conceptually, the endpoint can be written: &#xA; export function POST ( req : Request ) {&#xA; const bytes = new…

MySQL text ID collation: tread carefully

I was reading an article by PlanetScale , a hosted MySQL platform, called Why we chose NanoIDs for PlanetScale’s API . A decent enough article about Nano IDs and storing them in databases. &#xA; While there are no mistakes in the article, I think there is enough nuance omitted to warrant an addendum, otherwise others run the risk of misapplying the solution. &#xA; Here is the schema they used in…

Decapitation: a migration from antd to headless story

The modern web is so complex, there came a time when updating to either the latest Ant Design 4 (antd) , or latest Next.js 13.4 version would cause PDX Tools to completely break with a nonsensical error. Something about a tooltip. After a couple hours searching the web and tinkering, no solution was in sight. A frustrating and humbling experience (or humiliating depending on how you look at it).…

Advantages of opaque types over value objects

Most should be in agreement that the following signature is in bad taste: &#xA; type MyObject = {&#xA; price : number ;&#xA; }&#xA; Is the price in dollars, cents, or is it even some other currency? What&rsquo;s protecting another developer (including the future self) from footguns? &#xA; // Mixing up dollars and cents&#xA; const discountDollars = 2 ;&#xA; const productCents = 10000 ;&#xA; const…

Too edgy, a serverless search

There&rsquo;s one endpoint in PDX Tools that has been a bit of a thorn in my side. It&rsquo;s a critical, resource intensive endpoint that accesses proprietary embedded assets, but it&rsquo;s executed very infrequently. For a site that recently broke a million monthly requests, only about 100 of these are to this endpoint. &#xA; The endpoint in question digests an uploaded compressed file and…

There and back again with zstd zips

In pdx.tools , users have the option to upload their EU4 save files. Behind their .eu4 extension, these files are typically zips; zips that aren&rsquo;t compressed very well. Extracting and recompressing the files nets around a 20% reduction in size. &#xA; As the person who pays for file storage, storing poorly compressed zips isn&rsquo;t appetizing. Though I can understand why a game may prefer a…

New browser APIs unlock new possibilities

When new browser APIs get announced or are implemented, like the recent announcement from the Chrome team about WebGPU, there is an inevitable push back: &#xA; &#xA; [Is] this yet another information leak anti-feature that we need to disable? [source] &#xA; &#xA; &#xA; Nobody needs new fancy features, what people really need is more reliable fingerprinting [source] &#xA; &#xA; &#xA; The browser…

CycleList: a cyclical activity tracker

I, unironically, wrote a todo app: CycleList . &#xA; This was only after trying and failing to find an app or site where I could have a sequence of unscheduled tasks where completing a task would have it fall to the bottom of the list with an updated completed time. Tasks completed least recently then bubble up to the top, creating a sort of cycle. Hence the name, CycleList. &#xA; Use cases for…

Designing Responsive React Components for 2023

Designing responsive web sites is a tale as old as CSS media queries, but with the proliferation of JS component libraries, like React, there is a trap that is starting to become a thorn in the side of rising popularity of server-side rendered (SSR) React. &#xA; The trap is that conditionally rendering above the fold components based on values from runtime-derived media queries, results in a…

Wasm compression benchmarks and the cost of missing compression APIs

&#xA; &#xA; Interested in running the compression benchmarks yourself? It&rsquo;s hosted at bench.nickb.dev &#xA; &#xA; &#xA; Lack of transparent compression &#xA; IndexedDB , the Cache API , and network requests will not transparently compress data, and I think this is a problem. &#xA; Testing was done on Chrome and behavior may vary by browser. &#xA; Let&rsquo;s say we want a function to persist…

Rethinking web workers and edge compute with Wasm

Web workers are crucial for responsive client side compute, but they have a notorious ergonomic hurdle, so large that I&rsquo;ve previously debated whose responsibility it is to offload compute on the web: the library or the application developer. Not to spoil that article, but the decision is nuanced, essentially boiling down to: it is the library developer&rsquo;s responsibility if it is the…

Cloudflare Tunnel saved my home server when my apartment got a new ISP

My apartment building recently switched ISPs. After they finished installing, all the sites I host on my home server were no longer accessible. Cue slight panicking, as I host everything from code to documents to analytics to a dozen other use cases. &#xA; I thought my dynamic DNS client, dness , would save the day, but it had already detected, propagated the new WAN IP, and the problem persisted.…

Avoiding allocations in Rust to shrink Wasm modules

&#xA; Rust lacks a runtime, enabling small .wasm sizes because there is no extra bloat included like a garbage collector. You only pay (in code size) for the functions you actually use. &#xA; The Rust Wasm Book, &ldquo;Why Rust and WebAssembly?&rdquo; &#xA; &#xA; We can see small Wasm sizes in action with a contrived example: &#xA; use wasm_bindgen::prelude::wasm_bindgen;&#xA; &#xA;…

Favoring SQL over Redis for an evergreen leaderboard

At PDX Tools , one has the option to upload an EU4 save file and the app will deduce what achievements were earned and how many ingame days have elapsed in the save. &#xA; With this information we can create a leaderboard for individual achievements. &#xA; Here&rsquo;s the Postgres database schema to start us off, where we store the save id, the time it was uploaded, what achievements were…

DEFLATE yourself for faster Rust ZIPs

The Rust crate for handling ZIP files, zip-rs is quite flexible. The zip crate is compiled with builtin features to support deflated data (among other compression algorithms). This makes it incredibly easy to hit the ground running for reading and writing zips. &#xA; We can disable these builtin features. This may sound undesirable, but in fact, it is my new favorite way to integrate zip…

Avoiding dynamic CSS-in-JS styles in React

With React continuing to march towards a strong streaming server side rendering story, the React working group published a guide for how dynamic CSS-in-JS libraries can adapt . At the end of the guide, it gives a lukewarm sendoff to dynamic CSS-in-JS libraries: &#xA; &#xA; While this technique for generating CSS is popular today, we&rsquo;ve found that it has a number of problems that we&rsquo;d…

The footgun with Docker Compose shared configurations

Banner octopus graphic from the Compose repo &#xA; Docker Compose has a nice feature where several Compose files can be seamlessly fashioned together and allow for configuration reuse across environments. There is, however, counterintuitive behavior that can lead one to accidentally overwrite remote container images. Since I lost several hours to this, I figured I&rsquo;d write about it to sear…

The subjective nature of JS libraries exposing an off the main thread API

For the uninitiated, JavaScript environments like node.js and the browser have a main thread that runs basically everything. Exhausting the main thread can have consequences, as MDN puts it : &ldquo;long-running JavaScript functions can block the thread, leading to an unresponsive page and a bad user experience&rdquo;. In the context of this article, responsiveness describes the UI&rsquo;s ability…

Recommendations when publishing a Wasm library

Interested in seeing a library employing recommendations that will be laid out in this article? Check out jomini . The highwayhasher library takes this one step further by juggling an implementation that uses native code too! &#xA; Don&rsquo;t like reading a life story on recipes? Skip to the next section. &#xA; With Wasm support in nearly all JS environments , I&rsquo;ve become a large proponent…

Don&#39;t freak, but our Rust web server is now Node.js

A year and a half ago I wrote that My Bet on Rust had been Vindicated , as an incredible amount of code could be shared between the server and web client, and I could easily spin out C libraries and executables. No use case was out of reach. &#xA; But based on the title you know that the backend is no longer leveraging a Rust web server ( warp in this case). So what happened in the last year and a…

Accessing public and private B2 S3 buckets in Rust

The AWS S3 Storage API is ubiquitous and has been picked up by other 3rd party storage vendors like Backblaze B2, Minio, Wasabi, Storj, and IDrive. This is excellent for developers and sysadmins as it facilitates integration testing and experimentation with cloud storage providers. There is an AWS SDK available for 10 languages so chances are you can use the official SDK and connect it to a…

Authoring a SIMD enhanced Wasm library with Rust

Chrome, Firefox, and Node LTS have all stabilized the SIMD extension to Wasm in the last few months (Safari is lagging at the time of writing. See the updated roadmap for changes). Additionally, Rust has stabilized Wasm SIMD intrinsics recently too . All the pieces are set and now is the time to start authoring libraries that take advantage of the promised performance that SIMD can bring. &#xA;…

Replacing an unavailable ZFS drive

The day I&rsquo;ve known for a while has come: a drive in my ZFS array has become degraded. It&rsquo;s my first drive failure in over four years , not bad. &#xA; Here&rsquo;s the status report: &#xA; pool: tank&#xA; state: DEGRADED&#xA; status: One or more devices could not be used because the label is missing or&#xA; invalid. Sufficient replicas exist for the pool to continue&#xA; functioning in…

A workaround for Rust&#39;s lack of structural subtyping

This example from a Rust issue does not compile: &#xA; struct X { a: u8 , b: u16 , c: u32 , d: i8 } &#xA; struct Y { a: u8 , b: u16 , c: u32 , d: i8 } &#xA; let x = X { a: 1 , b: 2 , c: 3 , d: 4 }; &#xA; let y = Y { a: 5 , .. x };&#xA; The code does not compile as the struct update syntax, which is the ..x part from above, is contingent on the target struct being a subtype of the base (i.e. the…

Reality check for Cloudflare Wasm Workers and Rust

With native Rust support recently announced for Cloudflare Workers, I wanted to take a moment and write about the possibilities, but also the obstacles as a sort of reality check for myself. I&rsquo;m extremely bullish about Cloudflare, Wasm, Rust, edge computing, and the recently announced native Rust support. If I&rsquo;m not careful, my enthusiasm could cloud judgement, so I figured I&rsquo;d…