Disclaimer: This story is focused on the storytelling aspect of my experience. For the sake of flow and engagement, I’ve skipped detailed code explanations and some technical nuances. The goal here is to share the journey, not to provide a deep technical breakdown.
Introduction: A Lesson in Humility and TypeScript Wizardry
Every developer has that moment—the one where they think they've mastered something, only to be completely humbled by a colleague. This is the story of my moment. It started with a simple goal: improving routing in a React project. It ended with me questioning my entire existence as a developer. 😅
What follows is a journey filled with excitement, frustration, moments of pure dopamine-fueled breakthroughs, and ultimately, an eye-opening realization about experience, skill, and what it really means to be a senior dev. Buckle up. 🚀
The Setup 🎬
It was another day of development, another day of feeling like a pro. I was working on a React project, using createBrowserRouter
to handle the routing in Router.tsx
. Notice the .tsx extension? Yeah, that’s because createBrowserRouter
requires actual JSX components inside element
. Let me show you an example of what the router looked like:
export const router = createBrowserRouter([
{
path: "/",
element: <AppLayout/>,
children: [
{
path: "",
element: <ProtectedRoute><DashboardLayout/></ProtectedRoute>,
children: [
{
index: true,
path: "projects",
element: <OnlyAdmin fallback="/all-projects"><Projects/></OnlyAdmin>,
},
{
path: "projects/:projectId",
element: <OnlyAdmin><ProjectGuard/></OnlyAdmin>,
children: [
{
path: "groups",
element: <ProjectGroups/>
},
{
id: "project-stats",
path: "groups/:groupId",
element: <GroupGuard/>,
children: [
{
path: "stats",
element: <GroupStats/>
},
{
path: "consents",
element: <Consents/>
}
]
},
{
path: "stats",
element: <ProjectStats/>
}
]
},
fallbackRoute("/projects")
]
}
]
},
{
path: "*",
element: <NoMatch/>,
}
], {
future: {
v7_startTransition: true,
}
});
I loved using createBrowserRouter
. It was clean, it allowed nested routes, and I highly recommend it if you’re working with React Router. But then the problem hit.
The Problem 🤯
Across the project, we were using the <Link>
component to navigate. Pretty standard, right? Something like:
<Link to="/projects/123/groups/456">Go to Group</Link>
Now, here’s where the chaos began. The client requested a change in the routing structure. Just some small tweaks to paths, nothing major… right? WRONG.
Because now, every single manually written URL in the project had to be updated. 🥲
I was staring at the codebase, thinking: Okay, this is bad. This is really bad.
There had to be a better way—a way to validate links across the app without relying on hardcoded strings, a way to autocomplete routes when writing them, a way to ensure consistency.
My brainstorming mode activated.
🔍 How could I make the routes smarter?
💡 Could TypeScript help enforce route validation?
🤔 How do I make WebStorm autocomplete route names for me?
Then, the stupidest but funniest idea hit me: Should I just make a JetBrains plugin for this? 😂
For a solid five minutes, I was seriously considering writing an entire JetBrains plugin just to solve my routing issue. But then, I shook my head. Hichem, stop. The company is not paying you to build useless plugins.
The "Brilliant" Plan 🚀
Instead, I thought: let’s use TypeScript.
If I could somehow convert route paths into TypeScript-enforced IDs, I could:
✔ Validate links.
✔ Enable autocomplete.
✔ Ensure routes are always correct, no more broken links.
I modified the router to include unique IDs for each route:
const routeObjects: RouteObject[] = [
{
path: "/",
element: <AppLayout/>,
id: "root",
children: [
{
path: "projects",
element: <OnlyAdmin><Projects/></OnlyAdmin>,
id: "admin-projects-list"
}
]
}
];
I was feeling like a genius. But TypeScript wasn’t cooperating.
I needed TypeScript to dynamically recognize these IDs as valid values. I tried using typeof routesObject
, but nope, it didn’t work. After hours of searching the TypeScript docs (not great docs, by the way), tons of Stack Overflow pages, and even brainstorming with ChatGPT, I was getting nowhere.
That’s when the new idea hit: What if I just generate a TypeScript type file dynamically?
Boom. New plan:
- Extract all routes into an object.
- Generate a TypeScript type file dynamically.
- Run a script whenever routes change.
I wrote a script:
npm run generate-router-types
Which would output:
export type AppRoute = 'admin-projects-list' | 'home';
💥 Mission accomplished!
I pushed the PR, took a coffee break, and came back expecting a quick approval.
The "Joe" Moment 😐
Then I saw Joe’s comment. Let’s call him Joe (not his real name, but let’s roll with it).
"I have a better to do this, using only typescript"
My soul left my body. 💀
Bro, do you think I didn’t try that first?!
Then he messaged me directly:
"As-tu 15min pour discuter ta PR ?"
Great. A meeting. With Joe.
Joe was new at the company, but he had a crazy resume—Rust, Go, Node, DevOps, everything. But he also had that energy—the kind of dev who always has a counterpoint to everything.
Still, I joined the call with a whatever attitude. Let’s hear this genius out.
The Humiliation 🤡
Joe shared his screen.
He started explaining.
I saw his code.
And in that moment, I realized… I am, in fact, a junior. 😂
His solution? Pure TypeScript magic.
- No build scripts.
- No external tools.
- No route extraction function.
- Just a few lines of TypeScript.
type MergePath<T extends string | undefined, P extends string> =
T extends ""
? P
: P extends "/"|""
? `${P}${T}`
: `${P}/${T}`;
type ExtractRouteType<T extends RouteObject, P extends string> =
T extends { path: string } ?
{ route: P, paramsKeys: ExtractPathParams<P> }
: never;
type ExtractRoute<T extends RouteObject, P extends string> = (T extends { id: string, path: string } ? { [K in T["id"]]: ExtractRouteType<T, P> } : {}) & ExtractRoutes<T["children"], P>;
type ExtractRoutes<T extends RouteObject[] | undefined, P extends string = ""> = T extends [infer R extends RouteObject, ...infer S extends RouteObject[]] ? ExtractRoute<R, MergePath<R["path"], P>> & ExtractRoutes<S, P> : {};
export type RouteById = ExtractRoutes<typeof routeObjects> extends infer U ? { [K in keyof U]: U[K] } : never;
😭😭😭
I had spent hours on a solution that wasn’t even needed.
Joe laughed and said, "Yeah, TypeScript is really powerful when you know how to use it."
Yeah, I got schooled.
The Redemption Arc 🦸♂️
But instead of sulking, I fought back. I took Joe’s TypeScript wizardry and improved on it.
I thought: If he can dynamically extract routes, why can't I also dynamically validate parameters?
I wanted TypeScript to enforce route parameters, meaning:
✔ If a route requires :projectId
, I should be forced to pass { projectId: string }
as params.
✔ If I forgot a param, TypeScript should throw an error.
✔ If I added extra params that weren’t needed, TypeScript should complain.
✔ And of course, autocomplete should still work flawlessly.
I went back to my battle station. 💻☕
I introduced ExtractPathParams
, a TypeScript utility to extract only the required parameters from route paths:
type ExtractPathParams<Path extends string> =
Path extends `${string}:${infer Param}/${infer Rest}`
? [Param, ...ExtractPathParams<`/${Rest}`>]
: Path extends `${string}:${infer Param}`
? [Param]
: [];
Then, I rewrote buildRoute
so that it refused to compile if required parameters weren’t passed:
export function buildRoute<RouteId extends keyof RouteById>(
routeId: RouteId,
...params: ParamsArg<RouteId> extends undefined ? [] : [ParamsArg<RouteId>]
): string {
if (!routes[routeId]) {
throw new Error(`Route ${routeId} not found`);
}
let route = routes[routeId].route as string;
let params_ = params.length > 0 ? params[0] : {};
for (const [key, value] of Object.entries(params_)) {
route = route.replace(`:${key}`, value as string);
}
if (route.includes(':')) {
throw new Error(`Route ${routeId} contains an unresolved parameter`);
}
return route;
}
Boom. Now TypeScript would scream at me if I passed the wrong params! 🎯
I also refactored AppLink
to integrate this new validation:
const AppLink = <RouteId extends keyof RouteById>({
routeId,
...props
}: RouteBuilderProps<RouteId> & Omit<LinkProps, "to"> & React.RefAttributes<HTMLAnchorElement>) => {
return <Link to={buildRoute(routeId, props.params)} {...props}/>;
};
export default AppLink;
I ran the tests. Everything worked.
I opened another PR. This time, Joe actually liked it.
"Nice, I like this. Makes sure routes are truly type-safe. Good job."
😭 Sweet validation.
Now, instead of writing:
<Link to="/projects/123/groups/456">View Group</Link>
We could write:
<AppLink routeId="admin-project-group-consents" params={{ projectId: "123", groupId: "456" }}>
View Group
</AppLink>
✔ No more incorrect URLs.
✔ TypeScript-enforced validation.
✔ Autocomplete for route names & parameters.
✔ Less headache when changing paths in the future.
And just like that, the routing system was finally bulletproof. 🔥
The Lesson 🎓
That day, I learned: experience actually matters.
I could’ve held a grudge. I could’ve been bitter. But instead, I learned, improved, and built upon the knowledge.
The truth is, experience isn’t just about time spent coding. It’s about how deeply you challenge yourself, how far you push your skills, and how much you truly understand your tools.
That PR was a huge lesson for me. It wasn’t just about routing, or TypeScript, or Joe being a TypeScript wizard. It was about growth.
So if you ever find yourself humbled by a better solution, don’t resist it. Study it, build upon it, and make it your own.
That’s what makes a truly great developer. 😎
The End.
Top comments (25)
I'd argue that most junior part of this story is spending hours over hubris. Every dept/shop/team is different, but I tell my team if it takes more than hour, ask for guidance. Our budget has a limit.
To be fair that's definitely not junior typescript stuff hahaha. If you can write generic types like that without blinking a few times, I'd say you're an absolute typescript wizard.
Completely agree. Nothing in here is junior level.
Thx for a great story. I am arguing the typescript "wizardry" title. Because if we don't know how to use typescript to declare our special type with generic and condition, is just means one thing: we just put on hype but don't spend enough energy to know to use it. Most of typescript / JSDoc ( my favorite ) post are stop the explaining on basic types using mainly: string & number. But a ugly fact is our type ( this is a reall great example, thx again! ) are much-much more complex.
May I try to organize TS code a bit readable format.
This format is always working to me with a
?
&:
Have being working with TS in angular professionally, honojs and Nestjs recently. But TS generic codes like this still aches my brain.
Thanks.
Ok thanks for formatting this lol. Now this makes more sense without having to double take at what I was looking at
Pure gold! Thank you, kind sir.
By the way, just a comment: I am a software engineer and developer, have been for several decades, but I'm also a writer and editor, and I just want to praise you're writing. Very tight. Very nice. Brilliantly explicative and evocative. Kudos.
I thought this was a good piece about the dev-life, if you will. It was also personally encouraging. But, most of all I thrilled at a legitimate experience of aesthetic pleasure where I was most certainly not expecting it. I forget which (I want to say modern, if not contemporary) poet said a main object of his craft was to "surprise and delight" his audience.
Thanks a lot, I'm really happy to hear that ^_^
To be fair, most of the stuff that's happening here is pretty yucky and it's almost like React goes out of its way to make things as convoluted as possible. Most people would probably come out with pretty hairy solutions!
I'm literally experiencing this now. Today lol. It's amazing that this post showed on my timeline. I'm also using it as a learning experience. But yeah, that initial feeling of " man, I just did this"😁 to"oh,I could've just done.....😑"
Just use tanstack router
Im not even understand it yet, i just work with {data : any} right now 🤣 but still i'm improved by using type / interface for better validation,
i have skill issue so i use AI instead 😭😭😭
On complex type cases AI can help you, until a point, because TS is a bit complicated language and shard knowledge on internet also a different levels so for AI also hard to learn TS. This example is very fine.
JSDoc even worst in this case, but almost know every TS parts ( expect interface, enum ) but the JSDoc document is really weak. I using a TS doc for that also.
The param type matching was a nice touch, but the other types seem way over engineered. An actual Jr is going to have a to maintain this in a couple years. Why not just use an enum where the value is the URL and the name is the id?
const ROUTES = {
RouteID: URL
} as const
Then just replace all the URLs with the const value update your const object for the client request
@hichemtab-tech [ @greensmurph
It's hard for me to understand what is junior about spending mere hours over hubris. That must be because I live in a backward country that actually celebrates a senior citizen head of state that has lived steeped in hubris for many decades. And he is truthfully, and sadly, an excellent representative of the sentiment that my fellow countrymen seem to mostly express, if not in word yet deed.
That being said, I think your comment detracts from the humility, honesty, and skill with which this piece was written by @hichemtab-tech. I have studied code for programming on my own with an above average dedication of the average person interested in dev code, but I have passionately and relentlessly studied, poured over, lived, breathed, etc. the greatest accomplishments of the oldest code known to be recorded via long-term storage technologies: the written word. And this piece may well have been written by a young person, but Rimbaud, proved age doesn't even determine a junior in the most important and complex code of all: poetry. So, there is no argument for the author of this piece to be referred to as junior based on anything in this piece. @hichemtab-tech you coded this article with skill that could at least be evidence of a wizard in the making!
Disclaimer When it comes to code, my first and fondest love is maths.
Thank u, really happy to hear that ^.^
Some comments may only be visible to logged-in visitors. Sign in to view all comments.