bucketcode
- Duration
- 2 weeks
- Date
- August 2026



+3
Preface
Local-first is a good trade. Keep everything in IndexedDB and the app is instant, works offline, and nobody's notes sit on your server by default. Then the user gets a new phone, and the app is empty. IndexedDB is scoped to one origin, in one browser profile, on one device.
bucketcode is the small server-side piece that carries that state across. It serialises the app's local state into a self-describing snapshot, gzips it, and stores it in an S3-compatible bucket the developer controls, filed under a short sync code the user reads off the old device and types into the new one.
I extracted it from TripBrain, where it powers the cross-device sharing. It is published on npm as bucketcode under the MIT licence: bucketcode on GitHub.
Stacks involved
- TypeScript, Node.js 20+, ESM and CommonJS builds with bundled types
- AWS SDK v3 for S3, compatible with AWS S3, Cloudflare R2, MinIO, Scaleway, Wasabi
- tsup, Vitest, pnpm workspaces, Turborepo
- Fumadocs and Next.js 16 for the documentation site
- GitHub Actions, release-please, npm provenance
Key capabilities
- Snapshots with gzip compression and a self-describing envelope
- Schema version gating so an old app never loads data it cannot read
- Server-side expiry, enforced on read rather than left to bucket lifecycle rules
- Sync code generation paired with lenient normalisation (Crockford base32 by default)
- Conditional writes so two devices racing never silently lose data
- A plain file API underneath: upload, put, get, getUrl, delete
Details
The architectural choice is that the browser never talks to the bucket. Presigned uploads would mean CORS on the customer's bucket and client-side signing; bucketcode routes the payload through the developer's own API instead, which removes all bucket configuration at the cost of a size ceiling. The documentation states that ceiling per runtime (Lambda, Vercel, Netlify, Next.js) rather than hiding it, and explains that gzip's five to ten times ratio on repetitive JSON is what buys the headroom.
Sync codes are the whole user experience of moving between devices. Generation and normalisation come from the same configuration so they can never disagree about the alphabet. Confusable characters are folded only when the alphabet makes it unambiguous: "O" reads as zero only if there is no letter "O" to confuse it with. The code object also exposes its entropy in bits, so a developer can reason about what a shortened code is worth against guessing.
Every snapshot carries its own envelope format version, distinct from the app's schema version. Compression is detected from the gzip magic bytes rather than assumed, so older uncompressed snapshots still load. Keys are treated as untrusted input: traversal, control characters and over-long keys are rejected before anything touches the network.
Role
Solo project: library design and implementation, test suite, documentation site, two runnable examples and the release pipeline.
What I built
The library
- Snapshot API on top of a file API, with 13 stable error codes and the original error preserved as cause
- Expiry enforced on read: an expired snapshot returns null even while the object still sits in the bucket
- Conditional writes normalised against real S3 behaviour (412 and 409, quoted and unquoted ETags)
- Lazy client creation so the store is cheap to import at module scope during a Next.js build
- Escape hatch to the underlying S3Client
Testing without credentials
- An in-memory S3 double that honours conditional headers, so the concurrency paths are really exercised
- 121 test cases across 10 files, fully offline, run on Node 20, 22 and 24 in CI
- The same technique documented for consumers as a testing guide
Documentation and examples
- 17 documentation pages: getting started, core concepts, guides, use cases, API reference
- An encrypted-sync guide with browser-side PBKDF2 and AES-GCM so the server stores ciphertext it cannot read
- A Next.js example: a notes app in IndexedDB with sync routes
- A Node script proving the full round trip: compression ratio, sloppy code normalisation, both conditional writes, code burning
Release engineering
- release-please driven by conventional commits, path-scoped so only library changes cut a release
- PR title linting as the version signal, squash merges
- npm publish with provenance through OpenID Connect trusted publishing
Technical achievements
A narrow problem, solved completely
The scope is one thing: move local-first state across devices through a bucket you own. Everything around it (versioning, expiry, concurrency, code ergonomics, size limits) is handled and documented rather than left to the integrator.
Dogfooded in production
TripBrain uses bucketcode for its 8-digit share codes on Cloudflare R2, with one-hour expiry and conditional writes so a code is never overwritten.
In short
bucketcode is a focused, well-tested open-source library that gives local-first apps the one thing they are missing: a way to follow the user to their next device, without a backend and without handing credentials to the browser.






