Rank 23 of 34 · Tier B · Portfolio/DesignByDuhart/site, Portfolio/DesignByDuhart/neural-run, Portfolio/DesignByDuhart/CampaignStack: Next.js on Cloudflare Workers (OpenNext), D1, R2, Queues with a dead-letter queue, cron triggers, Auth.js, Stripe, D3, C++ compiled to WebAssembly
Where this sits. It is still not distributed systems in the sense the top
of this list is. It is, though, no longer a static site. It is a small
production system with a queue, a retry policy and an idempotency story, and
the most useful thing in it is a build gate that lied for three days and the
fix that means it cannot lie that way again.
A small system that refuses to post twice
Cloudflare Queues deliver at least once. A cron tick or a Post now click takes a conditional claim on the row before anything is enqueued; the consumer acks and stops if the post already has a remote id or the claim belongs to another attempt. The one window no local state can close, published then crashed, fails the row for a human instead of republishing.
What it became
On 27 August this was static HTML on Cloudflare Pages. On 5 September it moved
to a Cloudflare Worker built with OpenNext, because the site had started to
host things a static export cannot serve: an operator console behind sign-in,
server actions, middleware and a database.
designsbyduhart.org/* Worker designsbyduhart-site
public pages case studies, blog, demos, infographics, store
/console/* operator console, middleware-gated
/api/stripe/webhook store orders
queue consumer + */5 cron campaign publishing
designsbyduhart.org/neural-run* Worker neural-run (more specific route wins)
bindings D1 (12 forward migrations) R2 media Queue + DLQ Assets
Roughly 50 pages and 24 route handlers. The second Worker, NEURAL//RUN, is a
separate Next app mounted on a path route rather than a subdomain, so it needed
no DNS change at all, and it runs its own Auth.js with cookies scoped to its
own path so the two sign-in systems on one domain never see each other.
The publishing pipeline, which is the systems part
The console schedules and sends posts to LinkedIn and Instagram, with images,
carousels and native video pulled from R2. The send path is a queue, and
Cloudflare Queues deliver at least once. So the design is about not posting
twice.
A cron tick or a "Post now" click takes a conditional claim on the
target row: one UPDATE ... WHERE remote_id IS NULL AND (claim is empty or stale), with a fresh random claim key. Zero rows changed means someone else
got there first, and the caller backs off.
Only then is a message enqueued, carrying the claim key.
The consumer acks and stops if remote_id is already set (it was published)
or if the row's claim key is not the message's (another attempt owns it).
Failures retry with backoff, three times, then land in a dead-letter queue.
One window stays open and the code says so: the platform accepted the post and
the Worker died before writing remote_id. No local state can close that. So
once the attempt budget is spent, the row is marked failed with a message
telling a human to check the platform before retrying. Double-posting is worse
than not posting, and the system is built around that ranking.
Both entry points call one function, claimAndEnqueue. Two copies of "take a
claim, then enqueue" would be two copies of the property that stops a duplicate,
and the one that drifted would be the one nobody read.
The gate that passed because it stopped looking
scripts/verify.mjs fails the build on a redaction leak, a broken internal link
or an external resource. It read out/, the static export directory. When the
Worker cutover removed static export, out/ stopped being written, but the old
one stayed on disk. The gate's only sanity check was that the directory existed.
For three days and about forty commits it reported the same file counts and
PASS without reading a byte of what shipped.
A missing gate gets noticed. A gate that passes because it stopped looking gets
trusted. The fix that matters was not the path. The gate now reconciles what
it scanned against the prerender manifest the build itself emits, in both
directions, and any mismatch is a hard failure. Pointing it at the wrong tree can
no longer produce a pass. check-verify.mjs then plants a leak, a broken link,
an external resource and a stale file, and asserts the gate refuses each one.
Deploying without surprises
Preview, then promote.npm run preview builds, uploads a Worker version
without deploying it, and smoke-tests that version's URL. npm run promote <id>
ships that exact version with no rebuild.
Previews share production's bindings, same D1, same R2. So middleware
returns 404 for /console on any host but the canonical one. Without that,
every preview URL is a second public door to the live console.
Production runs the working tree, not HEAD. A deploy guard names every
uncommitted path about to ship before anything uploads.
Never pipe a deploy into tail. The pipeline reports the last command's
exit code, so a failed deploy reads as success.
The GitLab pipeline builds, typechecks and secret-scans green, and its deploy
job has never run, because the two CI variables it needs were never added. That
is written down rather than hidden.
The store
Seven React components sold as TypeScript source. Stripe Checkout, a webhook
verified with WebCrypto, orders upserted into D1 so the webhook and the thanks
page can both write the same row in either order, and per-request zip delivery
of the kit. The Stripe account is shared with two other products, so every
handler filters on its own metadata source and lets everything else fall through
with a 200.
Demos that run the real thing
The Breach Protocol demo is a small transformer written in C++, trained natively,
and run in the browser as WebAssembly, with gradients checked in double precision
before training. NEURAL//RUN is a gamified study platform with a 3-D skill tree,
code that runs in a sandboxed worker, and end-to-end checks that pass against
production. Every demo has a share page with its own Open Graph card.
The campaign media pipeline
Every social post is rendered from data: D3 over a jsdom document, rasterised,
and piped as raw frames into ffmpeg. Writing a PNG per frame cost 79 seconds a
day of content, nearly all of it compressing frames that the next process
immediately decompressed. Piping raw RGB with proper backpressure brought it to
about 53. Two silent failures were found on the way, neither of which raised an
error: jsdom dropping an xml: attribute prefix, and an RGB pixel format read
against an RGBA buffer, which shears the image instead of failing.
The DNS migration, still the part worth reading
The apex domain must serve from Cloudflare. A custom domain there needs a CNAME,
and a CNAME is illegal at the apex (RFC 1034). The old DNS host had no
ALIAS or ANAME, and Cloudflare's CNAME flattening solves it. So the zone had to
move, and the plan is written so that step 2 is the one that breaks email if
you skip it:
Add the zone in Cloudflare.
Verify the imported records include Google Workspace mail: the MX record,
the SPF TXT, the site-verification TXT. A missing MX is broken email. Add them
by hand if the scan missed them.
Change nameservers at the registrar.
Delete the stale A record pointing at a dead droplet.
Attach the custom domains.
Mail was verified live after the move. Knowing that a nameserver change silently
takes your mail with it, and writing the check as a blocking step rather than a
footnote, is the same instinct as the gap registers in
the manuals.
The site is also deliberately off the app droplet. That box runs the dating
app's vhosts and tunnel, and a portfolio there would share their blast radius.
Use it for
A short answer to "tell me about something small you ran in production." The
at-least-once queue with a conditional claim, and the gate that reconciles
against the build's own manifest, are the two pieces of real systems work here.
The rest is the vehicle that delivers everything above it.