I'm a solo dev with zero users right now, and I just spent an afternoon on a decision most people would've hardcoded in five minutes.
Here's the s...
For further actions, you may consider blocking this person and/or reporting abuse
Putting the paywall on a remote switch is a clever way to avoid backend costs before you even have users, but be careful that the hack doesn't become a security liability as you scale. When you finally decide to build out a proper backend for the extension, handling authentication and database setup from scratch can be a massive time sink. That is exactly why we built PubliFlow, a Next.js and Supabase boilerplate that gets you past the initial setup phase so you can focus on your actual product. You can check it out at publiflow.vip if you want to save yourself weeks of backend configuration when you are ready to scale.
Appreciate the caution, and it's the right instinct โ but I think the switch is safer than it reads at first glance. The gate values aren't the security boundary. Whether a feature is currently paid is public-ish info anyway; the thing that actually matters is "does this Google account have a live subscription," and that's never trusted to the client. The extension asks the Worker, the Worker checks it, and the gate flags just ride along on that same response. A user editing their local storage can flip a flag, sure, but they can't mint a subscription โ so the worst case is they unlock a nudge, not the actual paid state. The client self-reporting Pro is exactly the hole I designed around.
On the "proper backend" part โ this is the fun bit, because I'm kind of betting I never build one. There's no database and no auth server by design. Auth is just Google identity + Stripe. User data lives in the user's own Google Drive, not mine (drive.file scope, I literally can't see their other files). The whole server side is one Cloudflare Worker + KV, well inside the free tier. So the "weeks of backend config" you're describing is the exact cost I'm structuring the product to never pay. If NotebookBloom ever needs Supabase-and-a-real-DB, something about my no-server bet went wrong, and honestly I'd love to hit that problem because it'd mean I have the users to justify it.
Not knocking the boilerplate though โ for anything with real relational data or multi-user collab, rolling auth + DB by hand genuinely is a slog, so I get why it exists. Just a different shape of product than mine. Thanks for actually reading the guts of the post.
That makes total sense, especially since the actual entitlement check is deferred to the Worker verifying the Google account subscription rather than relying on client-side flags. It is a smart separation of concerns that keeps the extension lightweight while maintaining a real security boundary. Just out of curiosity, how do you handle the edge case where the Worker is temporarily unreachableโdo you fail open or lock the user out completely?
Fail open, every time โ but "open" here means "keep whatever tier I last confirmed," not "hand everyone Pro." That distinction is the whole trick.
The status check is cached for 24h, so the Worker being down for a few minutes, or even a few hours, doesn't touch anybody's tier โ most sessions never hit the network at all. And when it does check and the request times out, or 5xxs, or there's just no connection, all of that lands in the same catch and the function returns the current tier unchanged. The only thing that ever downgrades someone is an explicit
pro: falsecoming back on a healthy 200. "I couldn't reach the Worker" and "the Worker says you're not paid" are two completely different signals, and I only act on the second one. There's also a hard 15s timeout on the fetch so a hung Worker can't freeze the panel into a spinner โ it aborts, falls into the catch, moves on.The asymmetry is on purpose. Worst case of failing open: someone who canceled keeps Pro until the next successful check โ call it 24h, less if the background alarm beats it. That's a rounding error in revenue. Worst case of failing closed: some Cloudflare hiccup locks a paying customer out mid-task, and now I've earned a 1-star review and a chargeback for an outage that was my fault, not theirs. I'll eat a day of leaked Pro over that trade every single time. Punishing paying users because my infra sneezed is about the dumbest own-goal a solo dev can score.
Caching the entitlement check for 24 hours and defining fail-open as maintaining the last known state rather than defaulting to premium is a brilliant UX compromise. It essentially shifts the failure mode from a hard blocker to graceful degradation, which is exactly what you want for a client-side extension where network reliability is never guaranteed. Have you considered adding a subtle UI indicator when the cache is stale, just so users know they are operating on borrowed time if the Worker stays down for days?
Yeah, I keep circling this one, and I've mostly landed on "no persistent badge" โ for the same reason I fail open instead of closed. I don't want to turn my infra having a bad day into a thing the user has to sit there and think about.
The wrinkle that makes it trickier than it sounds: the cache is 24h by design, so "stale" is basically the normal state, not the exception. Most sessions never touch the network at all. If I lit something up every time the cached value was more than a few hours old, it'd be glowing almost all the time, for everybody, during completely healthy operation โ and a warning that's always on is just wallpaper. People stop seeing it in a day. Worse, think about who'd actually be staring at it: a paying customer whose subscription is totally valid, getting told they're "on borrowed time" because my Worker hiccuped. That's me manufacturing anxiety about an outage that isn't their fault and doesn't even affect them. Same own-goal as locking them out, just quieter and more passive-aggressive.
Where I'd lean, if I surface staleness at all, is the passive version of your idea, not the warning version. There's already a Refresh button in settings that forces a check on demand, and the panel quietly re-confirms when you switch back to it, on top of the 12h alarm ticking in the background โ so in practice you're almost never on genuinely old data unless the Worker is actually dead, not just slow. If I added anything it'd be a quiet "last checked X ago" line for the people who go looking, never a banner shoved in everyone's face. And the days-long-outage case you're describing is really the tell here: if my Worker is down for days, the user should not be finding that out from a badge. That's my monitoring's job, not theirs. The day my users turn into my alerting system, I've already lost.
zero users and already spending an afternoon on the paywall decision, i've been exactly there, agonising over stuff nobody's hit yet. putting it on a remote switch so you can flip it later is the kind of move i wish i thought of before hardcoding things.
ha, "agonising over stuff nobody's hit yet" is going straight on my tombstone. and honestly โ full confession โ i only built the remote switch because i'd already hardcoded a paywall in an earlier project and lived the pain. changing one price meant a new build, a resubmission, and waiting on a store review. for a pricing tweak. never again.
that's the part that flipped it from "nice-to-have" to "non-negotiable" for me: it's a browser extension, so anything hardcoded is hostage to the store review queue. the switch isn't really about indecision, it's about not letting a review gate stand between me and a config change. price, which features are free, even grandfathering old users โ all just values i flip server-side now, no redeploy.
the trap you and i both know: at zero users none of this matters yet. i keep having to remind myself the switch is cheap insurance, not permission to keep fiddling with it. what'd you end up doing on yours โ bite the bullet and rip out the hardcoded bits, or leave them till it actually hurts?
๊ฐ๊ฒฉ ๊ฒฐ์ ์ ์ฝ๋๊ฐ ์๋๋ผ ๋๋๋ฆด ์ ์๋ ์ค์ ์ผ๋ก ๋ง๋ ์ ์ด 1์ธ ์ ํ ์ด์์ ํนํ ์ ์ฉํด ๋ณด์ ๋๋ค. ๊ตฌ๋ ์ํ ๊ฒ์ฆ๊ณผ ๊ฒ์ดํธ ์ค์ ์ ๊ฐ์ ์๋ต์ ์ฃ๋, ์ค์ ๊ถํ์ด ํ์ํ ํด๋ผ์ฐ๋ ๋์์ ์๋ฒ์์ ๋ค์ ํ์ธํ๊ณ ๋ก์ปฌ ์ ์ฉ ๊ธฐ๋ฅ์ โ์๋ฒฝํ ์ฐจ๋จโ๋ณด๋ค ์ ํ ์คํ์ ์ ํธ๋ก ๋ค๋ฃจ๋ฉด ๋ณด์๊ณผ ์คํ ์๋๋ฅผ ํจ๊ป ์งํฌ ์ ์๊ฒ ์ต๋๋ค.
์ ํํ ๊ทธ ๋ ์ธต์ ๋๋ ์ ๋ด์ฃผ์ ์ ๋ฐ๊ฐ์ ์ต๋๋ค. ๋ง์ํ์ ๊ฒ ์ ๊ฐ ์ค์ ๋ก ๋ด๋ฆฐ
์ค๊ณ ๊ฒฐ์ ๊ณผ ๊ฑฐ์ ๊ทธ๋๋ก ๊ฒน์น๋๋ผ๊ณ ์.
'๊ฐ์ ์๋ต์ ์ค์ด๋ผ'๋ ๋ถ๋ถ์ ์ด๋ฏธ ๊ทธ๋ ๊ฒ ๋ผ ์์ต๋๋ค. ํ์ฅ ํ๋ก๊ทธ๋จ์ด ์ด์ฐจํผ
/status๋ฅผ ํธ์ถํด์ "์ด ๊ตฌ๊ธ ๊ณ์ ์ ๊ตฌ๋ ์ด ์๋"๋ฅผ ํ์ธํ๋๋ฐ(ํด๋ผ์ด์ธํธ ์๊ฐ
๋ณด๊ณ ๋ ๋ชป ๋ฏฟ์ผ๋๊น์, ๊ทธ๊ฒ ๋ฐ๋ก ํฌ๋ ๋๋ ๊ธธ์ด๋ผ), gate ๊ฐ์ ๊ทธ ์๋ต์ ์น์ด
๋ณด๋์ต๋๋ค. ์์ฒญ์ด ํ๋๋ ์ ๋์ด๋๋ค๋ ๊ฒ ์กฐ์ฉํ ์๋๊ฑฐ๋ฆฌ์์ด์.
๊ทธ๋ฐ๋ฐ ๋ ๋ฒ์งธ ๋ฌธ์ฅ์ด ์ ์ค๊ณ์์ ์ ์ผ ์ฝํ ๋ฐ๋ฅผ ์ ํํ ์ง์ผ์ จ์ต๋๋ค. ์ง๊ธ gate
ํ์ ์ ์ ๋ถ ํด๋ผ์ด์ธํธ ์ชฝ์ ๋๋ค โ canUse(feature, isPro, gates)๊ฐ ๋ธ๋ผ์ฐ์ ์์์
๋์์. ๋คํํ ์ ์ ๋ฃ ๊ธฐ๋ฅ ๋๋ถ๋ถ์ด ๋ก์ปฌ ์ ์ฉ(Anki ๋ด๋ณด๋ด๊ธฐ, ์ธ์ฉ ๋ด๋ณด๋ด๊ธฐ,
ํ๋์์นด๋)์ด๋ผ ์ต์ ์ ๊ฒฝ์ฐ๊ฐ "๋๊ฐ devtools ์ด๊ณ gate๋ฅผ ๋ค์ง์ด์ ๋ก์ปฌ ๊ธฐ๋ฅ ํ๋
๊ณต์ง๋ก ์ด๋ค" ์ ๋์ ๋๋ค. ์ฌ๊ณ ๊ฐ ์๋๋ผ ์๋ ์ ๋์ฃ . ๊ทธ๋์ ์ง๊ธ์ ์๋์ ์ผ๋ก ๊ทธ๋ฅ
๋๊ณ ์์ต๋๋ค.
ํ์ง๋ง ์ง์ง ๊ถํ์ด ๊ฑธ๋ฆฌ๋ ํด๋ผ์ฐ๋ ๋์ โ ์ ํํ ๊ตฌ๊ธ ๋๋ผ์ด๋ธ ํด๋ผ์ฐ๋ ๋๊ธฐํ โ
์ ์๊ธฐ๊ฐ ๋ค๋ฅด๊ณ , ์ฌ๊ธฐ๊ฐ ๋ง์์ด ์ ์ผ ์ ํํ ์ง์ ์ ๋๋ค. ๊ทธ๊ฑด ํด๋ผ์ด์ธํธ๊ฐ "๋
Pro์ผ"๋ผ๊ณ ์ฐ๊ฒจ์ ํต๊ณผ๋๋ฉด ์ ๋์ฃ . ๋ง์นจ ๊ทธ ํ๋ฆ์ ์๋ ์๋ฒ๋ฅผ ํ ๋ฒ ๊ฑฐ์นฉ๋๋ค:
๊ตฌ๊ธ ํ ํฐ์ ์์ปค๋ก ๋ณด๋ด๋ฉด ์์ปค๊ฐ ๊ทธ ํ ํฐ์ ๊ตฌ๊ธ์ ๊ฒ์ฆํด์ ์ง์ง ์ด๋ฉ์ผ์ ์ป๊ณ ,
๊ทธ๊ฑธ๋ก Stripe์ ๊ตฌ๋ ์ ๋์กฐํฉ๋๋ค. ๊ทธ๋ฌ๋ "๊ถํ์ด ํ์ํ ๋์์ ์๋ฒ์์ ๋ค์
ํ์ธํ๋ผ"๋ ๊ฑด ์ด๋ฏธ ๊ทธ์ชฝ ๊ฒฝ๋ก์์ ์ฐธ์ด๊ณ , ์์ผ๋ก ๋ก์ปฌ ์๋ ๊ธฐ๋ฅ์ ์ ๋ฃ๋ก ๋๋ฆด ๋๋
์ง์ผ์ผ ํ ์ ์ผ๋ก ๋ชป๋ฐ์ ๋๊ฒ ์ต๋๋ค.
์ ์ผ ๋ง์์ ๋ ๊ฑด ์ธ ๋ฒ์งธ ํ๋ ์ด๋ฐ์ ๋๋ค โ ๋ก์ปฌ ์ ์ฉ์ '์๋ฒฝํ ์ฐจ๋จ'์ด ์๋๋ผ
'์ ํ ์คํ์ ์ ํธ'๋ก ๋ณด๋ผ๋ ๋ง์. ์ ๋ ์ด๊ฑธ ์๊ทผํ ๋ฐ๋ ๋ฐฉํฅ์์ ์คํธ๋ ์ค ๋ฐ๊ณ
์์๊ฑฐ๋ ์. "๋ก์ปฌ gate๋ ์๋ฒฝํ๊ฒ ๋ชป ๋ง์์"๋ฅผ ๊ฒฐํจ์ผ๋ก ์ฌ๊ธฐ๊ณ ์์๋๋ฐ, ์ฌ์ค ๊ทธ
์ธต์์ ์ ๊ฐ ๋์ง๋ ์ง์ง ์ง๋ฌธ์ "์ด ์ฌ๋ ๊ฒฐ์ ๋ฅผ ๋ง์๊น"๊ฐ ์๋๋ผ "์ด ๊ธฐ๋ฅ์
์ ๊ทธ๋ ์ด๋ ๋์ง๋ฅผ ๋ถ์ด๋ฉด ์ ํ์ด ๋๋"์ ๋๋ค. ํ์์ ์์ ๋ฐฉ๋ฒฝ์ด ํ์ ์์ด์.
์๋ ๋ช ๋ช ์ด ์คํ ์ ํธ๋ฅผ ์ค์ผ์ํค๋ ๊ฒ๋ ์๋๊ณ ์. ์๋ฒฝํ ์ฐจ๋จ์ ๋์ด ์ค์ ๋ก ์
์๋ฒ๋ฅผ ๊ฑฐ์น๋ ๊ทธ ํ๋(๋๊ธฐํ)์๋ง ์ฐ๊ณ , ๋๋จธ์ง ๋ก์ปฌ ๊ธฐ๋ฅ์ ๊ฐ์ธ๊ณ ๋๋๋ฆด ์ ์๋
์ ํ ์คํ์ผ๋ก ๋๋ ๊ฒ โ ๋ฑ ์๊ธ์์ ์ ๊ฐ ๋งํ "๊ฐ๊ฒฉ์ ์ฝ๋๊ฐ ์๋๋ผ ์ค์ "์
์์ฐ์ค๋ฌ์ด ๊ฒฐ๋ก ์ด๋ค์. ๋ณด์์ ํ์ํ ๊ณณ์๋ง ์ฐ๊ณ , ๋๋จธ์ง๋ ์คํ ์๋๋ฅผ ์ํด ๋น์
๋๋ค. ์ ๋ฆฌํด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค โ ์ ๊ฐ ๋ ๊ฐ๋ฅผ ํ๋๋ก ๋ญ๋ฑ๊ทธ๋ ค ๊ฑฑ์ ํ๊ณ ์์๋ ๊ฑธ
๊น๋ํ๊ฒ ๊ฐ๋ผ ์ฃผ์ จ์ด์.