While browsing Twitter—sorry, X—I realized something that had never crossed my mind before: All profile pages live under https://x.com/:username. And it turns out that this is actually a major design flaw.

Route Hijacking
The problem with X’s routes is that they’re flat. Too flat.
- https://x.com/home takes you to your home page ✅
- https://x.com/messages takes you to your DMs ✅
- https://x.com/:username takes you to @username’s account ⚠️
So what if a username happens to match a reserved route? For example, what’s stopping someone from claiming @home and getting assigned the profile URL of https://x.com/home? Well, apparently nothing:
You remember your old friend @Settings, right?
Achievement unlocked: Hidden (or extremely popular) profiles. Depending on which route is registered last, you’re going to see one of two behaviors:
- Legitimate routes like
/homepoint to the hijacker’s profile. - Legitimate routes work, but you can no longer view the conflicting profiles.
Although the second approach is not ideal, it’s the lesser of these two evils. The first issue appears to have been patched, so you can no longer navigate to these profile pages directly because the reserved route always takes precedence. That leaves only two options for following these “hidden” accounts:
- Find or compose a tweet mentioning the account and hover over the mention.
- Find the account in someone else’s follows list.
If you try to view @settings’s followers, you’ll get a routing error:

But you can still view @home’s followers, at least for now. And there are lots of them: 1.1 million at the time when I wrote this, to be exact.

The issue hasn’t been fully patched, though, because if you click a mention for @logout, you’ll get a prompt to—you guessed it—log out. Likewise, mentions for @home and @settings will take you to /home or /settings, respectively.

Some usernames—like tos, messages, notifications, and privacy—appear to have been reserved correctly. Their mentions aren’t even hyperlinked:

Why It Matters
Users should never be able to hijack static routes during the account creation process, as this affects security and usability.
For example, at one point this bug prevented users from logging out of the mobile app because the legitimate /logout link would take them to @logout’s profile:
Hey @elonmusk, when you try to logout on the website on mobile it takes you to the @logout account instead
That may seem annoying, and it is. But it’s a mild regression compared to opening the app and seeing @home’s profile instead of your feed—an actual bug that was reported by multiple users in February 2024:


Now, imagine if the owner of the @home profile had included a malicious link in their bio or masqueraded as an official person. Sure, the account would’ve eventually been flagged and suspended, but maybe it would’ve managed to trick enough users to click the link before getting caught. That’s no longer an oopsie-woopsie: It’s a security loophole.
This bug also reveals an apparent disconnect between how users think navigation works and how it’s actually implemented on the web. Ordinary people don’t think of URLs the same way developers do, and the lines become even blurrier in a client-side-routed app like Twitter that tries its best to feel native. You open the “app” and you expect to see your timeline. You click a “tab”—not a link, mind you—and expect to see a new user interface without a full page reload. So you can’t really fault someone for logging in, seeing @home’s profile page, and thinking the site or their account has been “hacked.”
Why It Happened
Using dev tools, I verified that all responses on Twitter include the x-powered-by: Express header, which means the back end uses the popular Express.js framework:

With that in mind, we can assume that Twitter’s router looked something like this at the time when this bug was originally reported:
router.get('/:username', (req, res) => {});
router.get('/home', (req, res) => {});
router.get('/settings', (req, res) => {});
router.get('/logout', (req, res) => {});
In Express, route handling is done on a first-come, first-serve basis: The first route handler that matches a requested path will be the only one to run. This means that in the above example, a GET request for the /home route would call the handler for /:username because that route was defined first and matches the requested path. Effectively, this broke the app for everyone, showing them @home’s profile page instead of their feed.
Without seeing the source code, we can only make educated guesses, but it’s likely that Twitter’s solution was to flip the order of the routes and prioritize static ones:
router.get('/home', (req, res) => {});
router.get('/settings', (req, res) => {});
router.get('/logout', (req, res) => {});
router.get('/:username', (req, res) => {});
While that fixes the original bug, now nobody can view @home’s, @logout’s, and others’ profiles that happen to coincide with reserved routes.
As a best practice in API design, if you allow users to create public-facing accounts, their profile pages should live under a more specific path, like /user/:username, to minimize the risk of collisions with static routes:
router.get('/home', (req, res) => {});
router.get('/settings', (req, res) => {});
router.get('/logout', (req, res) => {});
router.get('/user/:username', (req, res) => {});
If you don’t do this, then you at least need to reserve these usernames so that users can’t claim them when signing up for an account or changing their handle.
Username Parking and Eviction
You might be thinking: Well, Twitter already fixed this bug, who cares? But suppose you’ve had @username for years, and then one day X decides to add https://x.com/:username as an internal route that just happens to conflict. If this route is registered with a lower priority than username routes, it’ll point to your profile page—the @home fiasco all over again. But if it’s implemented correctly, nobody will be able to view your profile from that point onward. Effectively, this one flaw in X’s routing forces you to change your handle. It also means you can compile a list of handles that may one day trigger route collisions and create an account for any that don’t already exist, “parking” them in the hopes of hijacking routes in the future.
Conclusion
All of this could’ve been avoided if Twitter had used URLs like /profile/:username or /user/:username for profile pages, as many social media sites do. I suspect that Twitter’s decision to use flat URLs was motivated by aesthetics: making profile URLs short instead of safe. Technically, that would’ve still worked if the developers had reserved these handles during account creation. But because they didn’t, I’m now a proud follower of @home and @settings.