Query Parameters vs Url Routing
Should state live in the path or in the query string? The decisive rule for when to route and when to parameterize.
The short answer
Url Routing over Query Parameters for most cases. Routes are the spine of your app — they're indexable, shareable, cacheable, and semantically owned.
- Pick Query Parameters if layering optional, combinable refinements on an existing resource — filters, sort order, search terms, pagination, UTM tags. State that doesn't define what the page IS
- Pick Url Routing if identifying a distinct resource or view — a product, a user, a category, a document. Anything that deserves its own canonical URL, its own cache key, and its own line in your sitemap
- Also consider: They are not rivals — they're layers. Route to the resource, parameterize the view. The mistake is using one where the other belongs: /products?id=42 should be /products/42, and /products/sort/price/page/3 should be ?sort=price&page=3.
— Nice Pick, opinionated tool recommendations
What each one actually is
URL routing maps a path segment to a resource or handler: /blog/2026/eunice-picks resolves to a specific thing. The path IS the identity. Query parameters hang off the end after a ?, as key-value pairs: /search?q=router&sort=new. They modify how an already-identified resource is presented or filtered. The tell is permanence and identity. A route answers "what is this page?" A parameter answers "how do you want it shown?" Confuse the two and you get the worst URLs on the internet — /page?type=product&action=view&id=993 — which is just a database query wearing a trenchcoat. Routing encodes meaning into structure; query strings encode adjustable knobs. One is architecture, the other is configuration. Treat them as the same tool and your URLs become unreadable, your analytics become unparseable, and your SEO becomes a rounding error.
SEO and shareability — where routing wins outright
Google treats clean paths as first-class and query strings as suspect. /best/cloud-providers reads as a topic; /index?cat=cloud&view=best reads as crawl-budget waste Google may ignore or collapse. Parameterized URLs invite duplicate-content hell: ?sort=price and ?sort=name return the same items in different order, and now you're explaining canonicals to a crawler that already moved on. Routes get indexed, cached at the CDN edge by path, and survive being pasted into a Slack channel without mangling. Query strings get stripped by overzealous link sanitizers, reordered by frameworks, and dropped from referrer headers. If a human might share it or a bot might rank it, it belongs in the path. The query string is where tracking junk and ephemeral filters go to be ignored — which is exactly correct, and exactly why you shouldn't put your actual content identity there.
Where query parameters earn their keep
Don't over-route. The classic disaster is cramming combinable state into the path: /products/category/shoes/color/red/size/10/sort/price is brittle, order-dependent, and explodes combinatorially. Every new filter forces a routing change. Query parameters are flat, optional, order-independent, and infinitely combinable — ?color=red&size=10&sort=price&page=2 just works, and dropping one is trivial. Filters, faceted search, pagination, sort order, feature flags, UTM tags, and transient UI state all belong here precisely because they're not identity, they're refinement. They also keep your route table small: one /products route handles a million filter combinations without a single new handler. The crime isn't using query params — it's using them for the resource itself. Refinements in the path is the same mistake as identity in the query, just inverted. Knobs go in the query string. Always.
The decisive rule
Route the noun, parameterize the adjective. If the value names a distinct thing that deserves a bookmark, a sitemap entry, and a CDN cache key, put it in the path — that's a route. If the value tweaks, filters, sorts, or annotates that thing, put it after the ?. /users/eunice is a route because Eunice is a resource. /users/eunice?tab=activity&sort=recent is correct because the tabs and sort are views of her, not new resources. Ask one question: "Is this a different page, or the same page shown differently?" Different page → route. Same page, different view → parameter. Get this backwards and you'll either drown in route handlers or publish URLs Google refuses to rank. Routing is the structure; query parameters are the settings dialog. Build the structure first, then bolt on the knobs.
Quick Comparison
| Factor | Query Parameters | Url Routing |
|---|---|---|
| SEO / indexability | Often ignored, collapsed, or flagged as duplicate content by crawlers | Clean paths indexed as first-class topics, ideal for sitemaps |
| Combinable optional state (filters, sort, pagination) | Flat, order-independent, infinitely combinable without new handlers | Brittle, order-dependent, explodes the route table combinatorially |
| Resource identity | Hides identity in a key-value soup — /page?id=42 | Encodes identity into structure — /products/42, the canonical URL |
| Shareability / cacheability | Stripped by sanitizers, reordered, dropped from referrers | Survives pasting, caches cleanly at the CDN edge by path |
| Tracking & transient UI state | Perfect home for UTM tags, feature flags, ephemeral knobs | Pollutes the route table with junk that should never be permanent |
The Verdict
Use Query Parameters if: You're layering optional, combinable refinements on an existing resource — filters, sort order, search terms, pagination, UTM tags. State that doesn't define what the page IS.
Use Url Routing if: You're identifying a distinct resource or view — a product, a user, a category, a document. Anything that deserves its own canonical URL, its own cache key, and its own line in your sitemap.
Consider: They are not rivals — they're layers. Route to the resource, parameterize the view. The mistake is using one where the other belongs: /products?id=42 should be /products/42, and /products/sort/price/page/3 should be ?sort=price&page=3.
Routes are the spine of your app — they're indexable, shareable, cacheable, and semantically owned. Query parameters are for refinements on top of a route, not a substitute for one. When you can't decide, the resource belongs in the path.
Related Comparisons
Disagree? nice@nicepick.dev