Understanding URL Query Strings vs Hash Fragments
The other day, I stumbled upon something interesting. I was checking out a survey and noticed two URLs that looked like they pointed to the same place:
https://example.com/page?panel_view=true
and
https://example.com/page#/?panel_view=true
At a glance, you might think: "Same link, right?" Well… not exactly.
Let's dig into what's going on here — and why this small detail might actually matter, especially if you're working with modern web apps.
The Curious "#"
In the second URL, you'll notice a hash (#
) followed by a slash:
#/?panel_view=true
That little hashtag, also called a hash fragment, plays a special role in web development.
🔍 TL;DR:
- Everything before the
#
= sent to the server - Everything after the
#
= stays in the browser
In other words: the server doesn't care about anything after the #
. It never even sees it. But if your app is a single-page application (SPA) — built with something like React, Vue, or Angular — it might care a lot.
So, Why Use the Hash at All?
Back in the day, #
was mostly used for scrolling to sections within a page (like #about-us
).
Now, in modern SPAs, it's often used for routing. When you see something like #/dashboard
or #/login
, that's your front-end framework deciding what content to show — without refreshing the page.
Do They Work the Same?
They might! Or they might not.
If the app is a traditional server-rendered page, it likely uses query strings (?
) to load different content.
If it's an SPA, the app might rely entirely on the hash (#/
) to trigger internal views.
Using the wrong one could lead to weird issues — like skipping required steps, not loading the right view, or even breaking analytics.
What About Caching?
Great question. Here's where things get even more interesting:
🧠 Browsers and CDNs treat query strings and hash fragments very differently when it comes to caching.
Query strings (
?panel_view=true
) affect the full URL — including server-side caching and CDN behavior. So: Different query strings = different cached versions. This can be good (for dynamic content), but sometimes bad (if it busts the cache unnecessarily).Hash fragments (
#
), on the other hand: Do not affect caching. The browser seeshttps://example.com/page#about
andhttps://example.com/page#contact
as the same resource — it just scrolls to a different part or changes state after the page loads.
💡 If you want to bust cache or serve different content from the server, use query strings. If you only need to affect front-end behavior, use hash fragments.
Catching These in Next.js
If you're using Next.js, here's how you grab each type:
✅ Query Strings
Next.js handles query strings natively using the useRouter
hook:
import { useRouter } from 'next/router';
const Page = () => {
const router = useRouter();
const { panel_view } = router.query;
return <p>panel_view is {panel_view}</p>;
};
📦 Hash Fragments
Hash values aren't part of Next.js routing — they live only in the browser. You'll need to use window.location.hash
:
import { useEffect, useState } from 'react';
const Page = () => {
const [hash, setHash] = useState('');
useEffect(() => {
setHash(window.location.hash);
}, []);
return <p>Current hash is: {hash}</p>;
};
Just remember: since this uses window
, it must run client-side only (e.g., inside useEffect
).
A Tiny Detail, A Bigger Lesson
This small difference is a great reminder that URLs are more than just links — they carry instructions. And depending on how your app is built, the format of those instructions can change what users see (or don't see).
Next time you copy-paste a link, give it a second look. That tiny #
could be doing a lot more than you think. 😉
Ever been bitten by a query string or hash behavior in a project? Drop a comment — I'd love to hear your story!
Top comments (0)