Rendering a page should not change anything. Nakodo breaks that rule in five places, and every one of them is on purpose, so I went and wrote down the rule they actually follow.

The app runs outreach campaigns for brands: it finds creators or local businesses that fit a brief, writes to them, and hands over the ones that say yes, which is the bit described on how it works. Every page in this post is behind the login, which turns out to be half the reason the pattern is safe.

One: the first page you load creates your account row

export const requireUser = cache(async (): Promise<SessionUser> => { const user = await getUser(); if (!user) redirect("/login"); if (!(await getProfile(user.id))) await db.insert(profiles).values({ id: user.id }).onConflictDoNothing(); return user; }); 

Sign-up creates a profile row. Accounts created any other way, by an operator or at the command line or by a provider that let somebody in through a path we did not build, do not have one, and the first page they load makes it.

The cost on the ordinary path is zero extra queries, because getProfile is what the app's layout reads anyway and both functions are wrapped in React's cache, so the whole thing is one lookup per request however many components ask. The insert is onConflictDoNothing, so two parallel requests from the same new account produce one row and no error.

Two: your billing period rolls forward the first time it is read

if (sub.currentPeriodEnd <= now) { let start = sub.currentPeriodStart; let end = sub.currentPeriodEnd; while (end <= now) { start = end; end = addMonths(start, 1); } [sub] = await db.update(subscriptions).set({ currentPeriodStart: start, currentPeriodEnd: end }) .where(eq(subscriptions.userId, userId)).returning(); } 

Usage limits count from currentPeriodStart, and on a free plan there is no Stripe webhook to move it. So the period moves when somebody reads it, in a while loop rather than a single addition, because an account that comes back after four months needs the current window and not the next one after the one it missed. That mechanism has its own post, including what it does about a signup on the 31st.

What matters here is the shape, which is the same as the first one: a predicate that is false in the steady state, and a write that only happens on the edge.

Three: a cut-off research run is put right by the next person who looks at the list

Research runs inside the request that started it, up to about two minutes of crawling and model calls. If that request dies, the campaign row is left saying researching and nothing in the system is watching it.

// Research cut off mid-run is put right only when the list shows some, so // the page doesn't write on every visit. const list = listed.some(researchIsStale) && (await recoverStaleResearch(user.id)) > 0 ? await campaignsNow() : listed; 

researchIsStale is a pure function over rows the page has already loaded: status researching and updatedAt more than three minutes ago. Only if one row in the list matches does the write happen at all, and the write is a single statement whose WHERE repeats the same conditions, scoped to the signed-in user:

await db.update(campaigns).set({ status: sql`case when ${campaigns.brief} is null then 'failed' when ${campaigns.launchedAt} is not null then 'paused' else 'draft' end`, pauseMode: sql`case when ${campaigns.brief} is not null and ${campaigns.launchedAt} is not null then 'both' end`, pauseReason: null, ... }).where(and( eq(campaigns.userId, user.id), eq(campaigns.status, "researching"), lt(campaigns.updatedAt, new Date(Date.now() - STALE_RESEARCH_MS)), )).returning({ id: campaigns.id }); 

Three CASE expressions instead of three round trips, because where the campaign should land depends on what it already had: a campaign with no brief failed, a campaign that was live comes back paused with everything stopped for a person to resume deliberately, and anything else is a draft. Then each recovered campaign refunds the research run it never finished, so a crash does not cost somebody a unit of their plan.

The honest part: this repair only runs if somebody looks. There is no cron sweeping for stale research. An account that starts a campaign, closes the tab at the wrong second and never comes back has a campaign that says researching until the day it loads a page. For a state that only exists because a request died, and that is only visible on the page that fixes it, I think that is the right trade, and I would not have believed it was deliberate if I had found it without the comment.

Four: a page read fetches a logo from somebody else's website, after it has answered

This one looks worst and I like it most.

after(() => fillLogos(list)); 

after is Next's post-response hook: the callback runs once the response has been flushed, so the reader waits for none of it. Inside:

const findingLogos = new Set<string>(); export async function fillLogos(list: {...}[]) { const todo = list.filter((c) => c.logoUrl === null && c.status !== "researching" && !findingLogos.has(c.id)).slice(0, 5); await Promise.all(todo.map(async (c) => { findingLogos.add(c.id); try { const logo = await findSiteLogo(c.domain).catch(() => null); await db.update(campaigns).set({ logoUrl: logo ?? "" }) .where(and(eq(campaigns.id, c.id), isNull(campaigns.logoUrl))); } finally { findingLogos.delete(c.id); } })); } 

Campaigns created before the app kept logos have logoUrl of null, which means "never looked". The first page that would display one goes and looks, which is the only honest way to backfill a column whose value lives on another company's website and that nobody should have to wait for.

Four guards, and each one is answering a different "what if":

  • logoUrl === null is the predicate. The steady state writes nothing, and "" is the marker for "we looked and found nothing", so a brand with no usable icon is asked about once, ever.
  • .slice(0, 5) bounds the work. A list of twenty campaigns does not fire twenty outbound requests.
  • The module-level Set stops the same render pass and two quick page loads on one instance from doing the same lookup twice. It is per instance, so on a serverless platform it is a hint and not a lock.
  • The WHERE carries isNull(campaigns.logoUrl) as well, which is the actual lock: whichever instance writes first wins, and the second update touches no rows.

The thing I had not thought about until writing this down: the app-router prefetches on hover, so moving your mouse over a campaign link can run the layout, and the layout is the second fillLogos call site. A prefetch can start a logo lookup. Because the work is idempotent, bounded and once-per-campaign, that is harmless, and if any of those three stopped being true it would be a bug that only ever happened to people who move their mouse.

Five: opening a support thread is what marks it read

const lines = await linesOf(id); // Reading it is what clears the mark. Done after the lines are in hand, so a // failure here leaves the mark rather than hiding an unread reply. if (options.markRead && summary.unread) { await db.update(supportTickets).set({ readAt: new Date() }).where(eq(supportTickets.id, id)); } 

getThread has exactly two callers, the page that shows a thread and the action that replies to it, and both pass { markRead: true }. The option exists anyway, because the admin panel counts tickets rather than opening them today, and the day an operator view does open one, the mark-read has to be a thing it can decline: an operator reading your ticket must not clear your unread dot.

The ordering comment is the whole decision. Load the messages first, then clear the mark, so if the query that clears it fails the user sees the messages and keeps the unread dot. The other order loses a reply quietly, and the unread state in this app is read_at is null or last_message_at > read_at, which means "read" is a claim about an instant rather than a boolean, and a wrong claim is permanent.

The rule all five follow

Reading them together, there are four properties, and I would now refuse a sixth one that broke any of them:

  1. A predicate that is false on the ordinary visit. !profile, periodEnd <= now, some(isStale), logoUrl === null, unread. If a page writes on every read, it is not a repair, it is an unlogged mutation.
  2. Idempotent, and safe when two requests race. onConflictDoNothing on one, the same conditions repeated in the WHERE on the other three, a single statement for the fourth.
  3. Scoped to the reader. Every one of these WHERE clauses carries the signed-in user's id, so the worst a read can do is repair its own account's rows. None of these paths is reachable without a session: the auth boundary is one allowlist and none of these pages are on it.
  4. Anything slow goes after the response. The only one that touches the network is the only one wrapped in after.

And one property they all lack, which I am leaving alone: none of them writes an audit row. The audit log is for things a person or an operator did, and "the system noticed something was stale" is not one of those. The repair is visible in the row it fixed, and if I needed a trail of repairs I would want it in the run log, not in the log that answers "who changed my account".

Five reads that write, in 72,683 lines. The reason to count them is that each one was obviously correct on the day I wrote it, and the fifth one is only obviously correct because of a comment about the order of two queries.