Skip to content
🛡️ FREE Master Frontend Security · All 7 modules live · 100% free Start free →
Episode 44 53 minutes

URL State at Scale with Francois Best

Key Takeaways from our conversation with François Best

François Best

Freelance web developer, founder of 47ng, creator of nuqs

Señors @ Scale host Neciu Dan sits down with François Best, freelance web developer, founder of 47ng, and the creator of nuqs — the type-safe search param state manager for React with over 10,000 GitHub stars, used by Sentry, Supabase, Vercel and Clerk. François started nuqs during the pandemic to move a woodworking calculator's state from his laptop to his phone, left it sitting on GitHub for two years, and rewrote it into a framework-agnostic library after Next.js shipped the app router. From the async scheduling queue that works around browser History API rate limits, to render count budgets enforced in CI, to the poisoned GitHub Actions cache behind the TanStack compromise, to running open weight models locally for clients who can't send code across the Atlantic, this is the URL-as-state conversation from the person who had to make it actually work.

🎧 New Señors @ Scale Episode

This week, I spoke with François Best, freelance web developer, open sourcer, and founder of 47ng, where he builds web applications with a privacy-first approach. François is the creator of nuqs, the type-safe search param state manager for React that has become the default for anyone who believes the URL is state you can actually use. It started as a way to get a woodworking cut list from his laptop down to his phone in the workshop, sat unused on GitHub for two years, and now supports Next.js, React Router, Remix, TanStack and more.

In this episode, we dig into why the URL is the right place for shareable state, the scheduling queue that keeps browsers from crashing under rapid history updates, the 90% of nuqs that nobody uses, how he tests render counts across every framework in CI, exactly how the TanStack supply chain attack worked, and why he's spent the last two months running models on his own machine.

⚙️ Main Takeaways

1. nuqs is useState, except the state lives in the URL

The one-sentence definition, and the very unglamorous problem behind it.

  • The pitch: "nuqs is useState from React, but it lives in the URL. You put the state of your application, instead of living in memory, it lives in the URL, so you can share it, bookmark it, reload it, send it to a friend."
  • The actual origin: A woodworking calculator on his own site. He'd do all the measurements on his laptop, then needed the cut list on his phone in the workshop. "I didn't want to set up a server, a database, all those stateful things that we generally use." The URL was already a transport.
  • The two dormant years: First versions during the pandemic in 2020, then it sat on GitHub until he started reaching for it across his own and clients' projects, put it on npm, and other people started telling him they were using it in production.
  • What forced the real work: September 2023, when Next.js made the app router generally available. He wanted to move his own site over and hit walls with the old pages-router APIs.
  • The decision that mattered: Rather than a breaking switch to the app router, he made it work with both simultaneously, since a single Next app can use both. "I wanted a way that could work no matter where you consume the components — to be completely router agnostic."

2. The adapter model opened it up to all of React

Version 2 was the pivot from "a Next.js library" to an ecosystem one.

  • What an adapter is: A small piece of code that teaches nuqs about your router and vice versa, loaded via a provider you wrap the app in.
  • What it supports now: React Router v5 (unofficially), 6 and 7, Remix 2, plus optional support for Waku, Inertia, and TanStack. Remix 3 isn't React anymore, so it's out.
  • The honest note on TanStack: "Technically you don't really need nuqs in TanStack because they already have great type safe APIs for URL state in there." It's there mostly so external components that use nuqs keep working.
  • How the breaking change landed: It was the only major one, and people took it well. "The upside and downsides were very in favor of the upsides" — and everyone already expects to wrap an app in a provider.

3. The URL is the source of truth, but updates are asynchronous on purpose

This is the part that makes the library hard, and it comes down to a browser limit most people never hit.

  • Precedence: If the URL changes externally — a user action, a server redirect — that wins.
  • Why writes are queued: Browsers rate limit the History API. "If you call it too fast, it might crash the page." Safari allows roughly 120 milliseconds between updates on average, and a slider or ordinary typing blows straight through that.
  • The engine: A scheduling queue with throttling, plus optional debounce semantics layered on declaratively when you only want the final value after typing stops.
  • What state you actually read: The internal representation of the scheduled update merged with the current URL. "Based on this queue and how it resolves, this is how we derive the state that is returned by the hooks."
  • The cost in renders: An optimistic value comes back instantly, then the router re-renders when the URL catches up. "For any update, at the minimum you have two renders." Some alternatives do more.

4. Types were never a decision — they came from writing C

On being credited with pushing type safety in the ecosystem, which he rejects.

  • He declines the credit: "I don't think I'm a pusher in that sense. Other people pushed for type safety a lot before I did."
  • Where it comes from: "I started programming with C. So types is literally burned into my brain. When I start designing a program, it's like, what are the types looking like?"
  • The two ugly years: Doing web dev in plain JavaScript before TypeScript won, "where everything would break literally at runtime. That was horrible. We didn't have AI to help us track things. We didn't have tools to help us track things."
  • Why it matters more now: Types keep a refactor from breaking something far outside the blast radius — and give agents a real understanding of how the moving parts relate.
  • The requirement in one line: "If I change something in the URL, the way I pass a number, and suddenly I get a string back, obviously that's not gonna work well."

5. Standard Schema only solves half the problem

The reason he spent years turning down "why don't you just use Zod?"

  • Why not tie to one library: Validation is what nuqs does on the read path — parsing search params into real runtime types like numbers, dates, booleans, or a permitted subset of strings.
  • The gap: "Standard schema only gives you one way transforms. It only gives you the parsing part from string to data type. And nuqs needs both ways, and needs both ways synchronized."
  • What's missing from the ecosystem: A standard codec — a bijective transform between two types. "Effect does this, Zod codecs do this, but there is no standard codec implementation of the standard schema approach." He thinks it would be broadly useful, since serialization is everywhere.
  • Why it isn't slow: He caches deserialization and only re-parses when a key/value pair actually changed, and serializes once per update before merging the queue into a single string.

6. Render count is a tested budget, enforced in CI

An unusual thing to assert on, and he assert on it across every supported framework.

  • The test: End-to-end tests check that the render count doesn't change. "If I break the render count budget, I want to at least know about it" — and have it be intentional and noted in the release notes.
  • The track record: Stable since 2.0, across eight minors and dozens of patches.
  • The trade-off he lives with: Sometimes fixing a scheduling bug costs you an extra render. "That's the game of finding a balance."
  • Where the flake comes from: Not the tooling. React time-slices updates, so a CI runner starved for CPU can produce two renders where there was one. "It's kind of hard to replicate and to work around."
  • Why his e2e suite isn't flaky otherwise: There's no distributed system underneath. "It's just literally one application running on one server. The browser hits something, clicks a button, reads the URL." The tests aren't designed for humans — one big test button and a div that says pass or fail. The suites are shared across frameworks, with per-framework pages importing the components.

7. Claude Code migrated Cypress to Playwright on Christmas Day

His conversion moment on agents, and the limits he found right after.

  • The setup: He'd been dubious, having never gotten good results before. The test he set was migrating a large Cypress suite to Playwright — "There's a lot of them. I do not want to do this by hand."
  • The method: Do the work by hand for one framework, one component, one page, to show the agent exactly what's wanted, then hand it everything else. "It was literally on Christmas Day or the next day. Yep, okay, it did the work."
  • The payoff: Playwright is faster and more reliable, and it killed the flake he had with Cypress.
  • Where agents still fail him: Not on the core of nuqs. "Whenever I let them implement things, they always make a huge mess. So I always come and clean it up behind." The internals depend on how React schedules work, and "they always make assumptions based on everybody else's blog it's being trained on."
  • Where they're genuinely better now: Prototyping API designs. He used to write RFCs as GitHub discussions with a markdown code block and no implementation; now he has an agent build a throwaway prototype he can actually use before committing to a shape.

8. Put what's shareable in the URL — and nothing else

The design guidance, including a nice reframe of what "sharing" means.

  • The rule: Anything that can be shared. "A bookmark is sharing it with yourself in the future. Your browser history is your past self sharing it with you now."
  • The canonical case: Tables with filters, sorting, and a search input.
  • It's not new: "We've been doing this pattern forever before, in PHP, in Ruby on Rails. It's just the fact that React theoretically had to reinvent everything from scratch, and so we forgot everything from scratch."
  • Don't repeat the Redux mistake: "This is not something where you want to put anything that you don't want to lose when you reload the page. It has to be thought about. It has to be designed."
  • Why it took off with the app router: Sending data to a React Server Component was awkward, and "the URL is like the one thing that will be sent when you do send a request." That gives you a bidirectional channel — the server can redirect with search params the client picks up.

9. People use about 10% of nuqs

The hidden API surface, all derived from one descriptor object.

  • What most people touch: The hooks, for client-side read and write.
  • What they're missing: The object those hooks consume — keys mapped to parsers, options and defaults — can be derived into much more.
  • Loaders: Server-side parsing that takes whatever your framework exposes (the searchParams page prop in Next, the request in a React Router v7 loader) and hands back type-safe values.
  • Serializers: The counterpart. Give it state, get a type-safe link for an href or a Link.
  • Nearly framework-free: "You could theoretically use nuqs without React" with just the loaders and serializer, though the React peer dependency remains.
  • URL keys: Declaratively map a readable variable name to a short key in the URL, so you can keep URLs short and code readable without hand-mapping on every read and write. It came from a client need, and "now I use it a lot and I can't work without it."
  • What's coming: A unified API where one descriptor works as a hook, a loader, a serializer, and a Standard Schema validator, composable with .extend and .pick the way Zod schemas are.
  • His advice: "Go for the declarative way. Extract the descriptor object for your search params into a given file and derive things from it."

10. Client work and open source feed each other

The freelance model behind a library used by Sentry, Supabase, Vercel and Clerk.

  • Roughly fifty-fifty: A client for three or four months funds the next open source cycle, though often the two run in parallel.
  • Features come from real needs: URL keys came from a client doing that mapping constantly by hand. An adapter for a new framework benefits everyone.
  • Why he calls it a loop: "I can bring them help, but they also help the community by having me there."
  • What he sells now: Tech debt elimination — migrating off legacy React Router or the Next pages router — done "while the car is running on the highway," so the team keeps shipping features throughout, with measurable performance improvements as proof.
  • The job that disappeared: Joining a team to build MVPs. "Instead of me being the one prompting the AI, I can actually teach your existing developers how to do what I did before."
  • Listen to the 20%: Focusing on developer experience satisfies most people, "but there will always be 20% of people who try to really push it further. And those are the good people to listen to, because those are the people that help you polish the thing a lot."

11. Exactly how the TanStack attack worked

The clearest walkthrough of that compromise I've heard, and it's genuinely unsettling.

  • The single weak point: A pull_request_target workflow that ran against every PR.
  • The injection: The attacker opened a PR, which let them poison the GitHub Actions cache, then instantly closed it. "So they just saw a PR going up, down, without anything changed in the code base."
  • Why nobody caught it: "We can't expect them to have the same level of scrutiny for something that's just been opened and closed than for something that has real value in the code base."
  • The detonation: Five or six hours later, a completely legitimate release reused the poisoned cache, which injected code into the workflow, which stole the OIDC provenance credentials GitHub uses to authenticate against npm.
  • The camouflage: Malicious versions were published alongside the legitimate ones. They expected 1.2.3, saw 1.2.3, and something else went out in the noise.
  • The scary part: "The poison cache thing is completely untraceable. You cannot see what happened there."

12. Staged publishing plus reproducible builds is the defense he built

What he actually did about it, rather than what he'd like npm to do.

  • First move: He closed external PRs entirely while he shored things up. "Lock the gates, build the moats, build the castle, and then we can reopen it afterwards."
  • Staged publishing: Packages configured so they cannot be published directly to the registry — everything lands in a staging area first, and approval requires a 2FA TOTP code. Even a compromised workflow with a stolen token can't ship straight to users.
  • The scanning window: That pause is where you run something like Snyk or socket.dev and get a green light before approving.
  • His extra check: He downloads the staged tarball and does a reproducible build locally. "Unless my code base has been infected, then if it's just a workflow that injected something, it will not match what the reproducible build is locally on my machine."
  • Where the blame sits: "They need to revisit GitHub Actions entirely. This is remote code execution as a service by design." He also notes npm and GitHub are both Microsoft, "so I think Microsoft should pay a little more attention to their supply chain attacks."
  • The other attack surface: Most of these start with a maintainer's machine being phished or infected. Strong 2FA and hardware keys matter if you maintain anything with a public profile.

13. Scan responsibly, and don't turn open source into a slop firehose

On the agent-assisted security work he's doing, and the failure mode next to it.

  • What he built: A skill that runs deterministic linters over GitHub workflows, then uses an agent to navigate them the way an attacker would, based on known attack patterns. He ran it against friends' repos and disclosed the results privately. "It was like, yeah, this is actually helpful."
  • The warning attached: "Please don't spam them. Don't do the curl thing where Daniel Stenberg had to close his bug bounty program because he received a ton of slop spam vulnerability reports." The net result there is a less secure project, because legitimate white hats can no longer be paid.
  • On gating contributions: He likes Mitchell Hashimoto's vouch approach — say hi, show you're genuine, get approved, then submit PRs — over closing PRs or leaving GitHub.
  • The contributions that bother him most: Not the obvious AI slop, which is easy to handle in small volumes. "The most annoying part is when you have someone who legitimately wants to contribute something, but you can just tell that you're talking to their agent."
  • Why that matters to him: "Open source is not just about the code, it's about the relationship we have with the people who make it. This is more valuable than the code itself. The value in open source for me is the people, not the code."

14. Local models are now good enough for clients who can't send code abroad

Two months of dabbling that turned into a real offering.

  • The client reality: Especially in France, some companies happily throw everything at a cloud provider for the productivity, while others have certifications and regulations governing who can access what.
  • Where open weights landed: Running on consumer hardware, "maybe 80% as close as frontier models — maybe a six month lag in terms of capability. For a lot of different tasks, that is good enough."
  • The real constraint: Scale. "Your infra is literally the bottleneck for scale," unlike a cloud agent with GPUs to spare.
  • What it reminds him of: "When I started using agents, it feels like that. It feels like this level of capability now."
  • Where the fun is: Rearranging the key-value cache used for session storage and tweaking configurations to fit a larger model on a machine, or make a smaller one run faster.
  • The private use case: Something that runs offline against your own documents. "Chat with my taxes, for example — that I would never dream of sending to OpenAI or Anthropic."
  • On voice: He prompts by microphone because it's faster than typing, and runs transcription locally through Handy using models like Parakeet and Whisper, rather than uploading audio to a cloud that may train on it.

🧠 What I Learned

  • nuqs began as a way to move a woodworking cut list from laptop to phone, and sat on GitHub for two years before anyone used it.
  • Next.js shipping the app router in September 2023 is what forced the rewrite — and the decision to support both routers at once rather than break everyone.
  • Browsers rate limit the History API (Safari around 120ms between updates), which is why every nuqs write goes through a throttled async scheduling queue with optional debounce.
  • The minimum cost of a URL state update is two renders: the optimistic value, then the router catching up.
  • Standard Schema only handles one-way parsing; URL state needs a synchronized bijective transform, and there's no standard codec for that yet.
  • Render count is tested as a budget in CI across every supported framework, and has held stable since 2.0.
  • e2e tests aren't inherently flaky — flake comes from distributed infrastructure, and a suite with one server and one browser stays stable.
  • Claude Code migrated his whole Cypress suite to Playwright over Christmas, after he hand-did one framework as the example.
  • Agents still can't safely touch the nuqs core, because they reason from blog posts about React scheduling rather than the actual internals.
  • People use roughly 10% of nuqs; loaders, serializers, URL keys and Standard Schema support all derive from the same descriptor object.
  • The TanStack attack came through a pull_request_target workflow that poisoned the GitHub Actions cache from an opened-then-closed PR, detonating hours later during a legitimate release — and the poisoned cache is untraceable.
  • Staged publishing plus a local reproducible build catches a compromised workflow before the package reaches users.
  • Open weight models on consumer hardware are roughly six months behind frontier and good enough for a lot of client work where code can't leave the building.

💬 Favorite Quotes

"nuqs is useState from React, but it lives in the URL."

"I started programming with C. So types is literally burned into my brain."

"A bookmark is sharing it with yourself in the future. Your browser history is your past self sharing it with you now."

"We've been doing this pattern forever before, in PHP, in Ruby on Rails. It's just the fact that React theoretically had to reinvent everything from scratch, and so we forgot everything from scratch."

"If I break the render count budget, I want to at least know about it."

"Whenever I let them implement things, they always make a huge mess. So I always come and clean it up behind."

"There will always be 20% of people who try to really push it further. And those are the good people to listen to."

"The poison cache thing is completely untraceable. You cannot see what happened there."

"They need to revisit GitHub Actions entirely. This is remote code execution as a service by design."

"Open source is not just about the code, it's about the relationship we have with the people who make it. The value in open source for me is the people, not the code."

🎯 Also in this Episode

  • Why the library is called nuqs: it was next-usequerystate, which was unbearable to type, and the initials were free on npm — four letters, which is rare
  • The retrofitted backronym he closes talks with: never underestimate query strings
  • My own pre-2020 filter implementation that re-rendered five or six times per URL change and produced a new bug every week
  • The cat-and-mouse period with the Next.js team while the app router's internals were still shifting, and the current breakage between Next canaries and the React compiler
  • Discovering Cloudflare's V-Next fork of Next.js early, because he monitors new projects using nuqs and it turned up in their validation suite
  • Why he plans to drop React 18 in nuqs 3: useOptimistic and other React 19 hooks he's been working around
  • Why migration guides matter more than codemods now — an agent that reads a guide with examples and explanations is "codemods on steroids"
  • Blog recommendations: TkDodo's blog as the golden standard for React, with Dan Abramov close behind, and Aurora Scharff's for Next.js app router patterns
  • Both of us admitting the only books we finish now are the ones we read to our kids — currently The Hobbit — and a shared Lord of the Rings tattoo detour

Resources

More from François and the tools mentioned:

Books mentioned:

  • The Hobbit — currently being read aloud, which is the only reading either of us gets done

🎧 Listen Now

🎧 Spotify
📺 YouTube
🍏 Apple Podcasts

Episode Length: 53 minutes on URL state, the async scheduling queue behind it, render count budgets, the 90% of nuqs nobody uses, exactly how the TanStack supply chain attack worked, and running open weight models locally.

Whether you've hand-rolled filter state into a URL and regretted it, maintain a package that could be the next supply chain target, or want to know what local inference is actually good for now, this one goes deep on all three.

Happy sharing,
Dan

🛡️ FRONTEND SECURITY · REACT · VUE · ANGULAR · VANILLA JS

Master Security in Frontend Applications

Free, comprehensive frontend security course.
XSS, CSRF, AI security, broken access control & the vulnerabilities that actually get you breached.

100% FREE 7 MODULES · ALL LIVE
Start learning free →

All 7 modules live now. No credit card.

💡 More Recent Takeaways

Accessibility at Scale with Craig Abbott
Episode 48

Señors @ Scale host Neciu Dan sits down with Craig Abbott, Principal Accessibility Specialist at TetraLogical and the former Head of Accessibility at the UK's Department for Work and Pensions, one of the largest government departments in the country, where he built a dedicated accessibility practice from nothing and open sourced the DWP Accessibility Manual. Craig has over 15 years in user centred design and has led accessibility work across the public sector and at Elastic. From what sustainable accessibility actually means and why third party audits alone don't get you there, to the three C's of compliance, culture and capability, to running screen readers in VMs without expensive licences, to accessibility acceptance tests in CI with Playwright, Cucumber and Guidepup, to why compliance does not mean usable, this is the accessibility conversation for teams who want it to survive the person who cares about it.

Versatility at Scale with Carmen Huidobro
Episode 47

Señors @ Scale host Neciu Dan sits down with Carmen Huidobro, CTO at Incredible Bee in Vienna, where she builds products and helps teams figure out what should be automated and what should stay in the hands of users. Carmen has spent 17 years in tech, almost all of it freelancing, working across Objective-C, Ruby on Rails, the web, mobile, hardware, and even ABAP inside an SAP consultancy, plus five years in developer relations and developer education. Her argument is that the generalist versus specialist debate misses the point: the durable skill is being an expert at adapting. From adding a local Mistral model to a twenty-year-old macOS app without betraying the people who use it, to the refugee hackathon project the City of Vienna still runs a decade later, to why she won't take money from junior developers, this is a conversation about the skills that survive the shift.

CI/CD at Scale with Marko Gacesa
Episode 46

Señors @ Scale host Neciu Dan sits down with Marko Gaćeša, Head of Product at Semaphore, the agent-native CI/CD platform, and the first product guest on the show. Marko is a serial entrepreneur whose career spans developer tools, IoT, industrial automation and enterprise SaaS, including founding Dry Tools and serving as Chief Product Officer at Alchemy Cloud, with earlier work in domains like medical equipment where quality is non-negotiable. From why testing rather than coding is now the bottleneck, to what agent-native CI/CD actually means when developers live inside their coding agent, to how SemAI attacks flaky tests and automates migration off GitHub Actions, to pricing a platform where a minute of CI is not the same minute everywhere, this is the product side of developer tooling from someone shipping it.

AI Harness at Scale with Maxim Salnikov
Episode 45

Señors @ Scale host Neciu Dan sits down with Maxim Salnikov, AI Dev Tools Solution Engineer at Microsoft, where he leads AI native development enablement for over 100 enterprise customers of Microsoft and GitHub in Norway. Maxim has been building for the web since the late 90s and spends his days inside real enterprise dev teams across finance, energy, agriculture, and pure software companies, watching AI adoption succeed and fail. From why adoption is a change management problem rather than a technology one, to the anatomy of an AI harness and the external layer successful companies build on top of it, to the context engineer and agent ops roles now appearing in team topologies, to managing agent skills as versioned dependencies instead of letting them pollute the repo, this is the enterprise AI adoption conversation from someone who sees a hundred versions of it.

📻 Never Miss New Takeaways

Get notified when new episodes drop. Join our community of senior developers learning from real scaling stories.

💬 Share These Takeaways

Share:

Want More Insights Like This?

Subscribe to Señors @ Scale and never miss conversations with senior engineers sharing their scaling stories.