A presentation at DotJS in in Paris, France by Eduardo San Martin Morote
Eduardo San Martin Morote Frontend Nerd @posva 🔗 esm.dev
Modern Routing
Modern Routing
Modern Routing
Modern Frontend Routing
https://movies.com/search?genre=horror&page=1#filters
https://movies.com/search?genre=horror&page=1#filters History
https://movies.com/search?genre=horror&page=1#filters History Router
https://movies.com/search?genre=horror&page=1#filters History Matcher Router
https://movies.com/search?genre=horror&page=1#filters History Matcher Router Components
https://movies.com/search?genre=horror&page=1#filters History Matcher Router Components
History API history.pushState(data, title, url) history.replaceState(data, title, url) window.addEventListener(‘popstate’, handler) history.state history.length posva
https://movies.com history.state history.length null 1 posva
https://movies.com history.state history.length null 1 posva
https://movies.com history.state history.length null 1 history.pushState({ from: null }, ”, ‘/releases’) posva
https://movies.com /releases history.state history.length { from: null } 2 history.pushState({ from: null }, ”, ‘/releases’) posva
https://movies.com /releases history.state history.length { from: null } 2 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com /search?genre=horror&page=1#filters history.state history.length { from: ‘/releases’ } 3 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com /search?genre=horror&page=1#filters history.state history.length { from: ‘/releases’ } 3 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com /releases history.state history.length { from: null } 3 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com /releases history.state history.length { from: null } 3 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com /search?genre=horror&page=1#filters history.state history.length { from: ‘/releases’ } 3 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com/search?genre=horror&page=1#filters posva
https://movies.com/search?genre=horror&page=1#filters Path Query (or search) Hash (or fragment) posva
Path /search Query ?genre=horror&page=1 Hash #filters posva
Path /search Query ?genre=horror&page=1 Hash #filters location posva
Path /search Query ?genre=horror&page=1 Hash #filters location.pathname posva
Path /search Query ?genre=horror&page=1 Hash #filters location.search posva
Path /search Query ?genre=horror&page=1 Hash #filters location.hash posva
Path /search Query ?genre=horror&page=1 Hash #filters location.href posva
/search?genre=horror&page=1#filters posva
/search?genre=horror&page=1#filters { } path: ‘/search’, query: { genre: ‘horror’, page: ‘1’ }, hash: ‘#filters’ posva
?genre=horror&page=1 posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
posva
Encoding Character Hex Encoded ” U+0022 %22 < U+003C %3C ñ U+00F1 %C3%B1 posva
Encoding Character Hex Encoded ” U+0022 %22 < U+003C %3C ñ U+00F1 %C3%B1
<p class=”a”> posvaEncoding Character Hex Encoded ” U+0022 %22 < U+003C %3C ñ U+00F1 %C3%B1
<p class=”a”> %3Cp%20class=%22a%22%3E posvaEncoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%) posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%) Path Query Hash posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%) Path Query Hash ␣ ” < > ` posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%)
Path ␣ ” < > Query Hash ␣ " < >
posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%)
Path ␣ ” < > # ? { } Query Hash ␣ " < >
posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%)
Path ␣ ” < > # ? { } Query ␣ " < >
Hash ␣ ” < > `
posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%)
Path
Query
Hash
␣ ” < > # ? { } ␣ " < > # & = ␣ " < >
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>`’
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>` location.hash: ‘#%20%22%3C%3E%60’
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>` location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>%60 location.hash: '# "<>
’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>%60 location.hash: '# "<>
’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>%60 location.hash: '# "<>
’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash = encodeURI('# "<>
’)
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>%60 location.hash: '# "<>
’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' location.hash = encodeURI('# "<>
’)
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>%60 location.hash: '# "<>
’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60
location.hash: ‘# “<>' https://<…..># "<>
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' 🤔 location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' 🤔 location.hash: ‘# "<>
’
💯
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' 🤔 location.hash: ‘# "<>
’
💯
location.hash: ‘#%20%22%3C%3E%60’
✅
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>https://<…..># "<>
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' 🤔 location.hash: ‘# "<>
’
💯
location.hash: ‘#%20%22%3C%3E%60’
✅
location.hash: ‘#%20%22%3C%3E%60’
🤪
location.hash: ‘# “<>' https://<…..>#%20"<>
https://<…..># “<>https://<…..>#%20"<>%60 https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
Encode you URL even if it looks bad encodeURI / decodeURI encodeURIComponent / decodeURIComponent Path and hash Query keys and values posva
History { } path: ‘/movies/493922’, query: {}, hash: ” Communicate with the address bar Handle encoding Handle browser specific bugs/inconsistencies posva
{ { } path: ‘/movies/493922’, query: {}, hash: ” } path: ‘/movies/493922’, params: { id: ‘493922’}, name: ‘MovieDetail’, component: …, query: {}, hash: ” posva
Matcher { { } path: ‘/movies/493922’, query: {}, hash: ” } path: ‘/movies/493922’, params: { id: ‘493922’}, name: ‘MovieDetail’, component: …, query: {}, hash: ” posva
Matcher ‘/home’ /^/home$/i /^/movies/([^/]+)$/i ‘/movies/:id’ /^/movies/(\n+)$/i ‘/movies/:id(\n+)’ /^/movies/new$/i ‘/movies/new’ path-to-regexp posva
Matcher ‘/home’ ‘/movies/:id’ ‘/movies/:id(\n+)’ ‘/movies/new’ /^/home$/i ‘/movies/new’ /^/movies/([^/]+)$/i /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher ‘/home’ ‘/movies/:id’ ‘/movies/:id(\n+)’ ‘/movies/new’ /^/home$/i ‘/movies/new’ /^/movies/([^/]+)$/i /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher ‘/home’ ‘/movies/:id’ ‘/movies/:id(\n+)’ ‘/movies/new’ /^/home$/i /^/movies/([^/]+)$/i ‘/movies/new’ /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher ‘/home’ ‘/movies/:id’ ‘/movies/:id(\n+)’ ‘/movies/new’ /^/home$/i /^/movies/([^/]+)$/i ‘/movies/new’ /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher ‘/home’ ‘/movies/:id’ ‘/movies/:id(\n+)’ ‘/movies/new’ /^/home$/i /^/movies/([^/]+)$/i ‘/movies/new’ /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher 70 ‘/home’ 130 ‘/movies/:id’ 135 ‘/movies/:id(\n+)’ 140 ‘/movies/new’ /^/home$/i /^/movies/([^/]+)$/i ‘/movies/new’ /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher 140 ‘/movies/new’ 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 ‘/home’ ‘/movies/new’ posva
Matcher 140 ‘/movies/new’ 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 ‘/home’ ‘/movies/new’ posva
Matcher 140 ‘/movies/new’ 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 ‘/home’ posva
Matcher 140 ‘/movies/new’ 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 ‘/home’ 70 posva
Matcher 140 ‘/movies/new’ 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 ‘/home’ 70 + 70 70 posva
Matcher 140 ‘/movies/new’ 70 + 70 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 + 60 70 ‘/home’ 70 posva
Matcher 140 ‘/movies/new’ 70 + 70 135 ‘/movies/:id(\n+)’ 70 + 60 130 ‘/movies/:id’ 70 + 60 70 ‘/home’ 70 + 5 posva
140 70 + 70 135 70 + 60 130 70 + 60 70 70 ‘/movies/new’ + 5 posva
Router { } path: ‘/movies/new’, params: {}, name: ‘MovieNew’, component: …, query: {}, hash: ” posva
Router { } path: ‘/movies/new’, params: {}, name: ‘MovieNew’, component: …, query: {}, hash: ” View Component posva
Router { } path: ‘/movies/new’, params: {}, name: ‘MovieNew’, component: …, query: {}, hash: ” View Component posva
https://movies.com/search?genre=horror&page=1#filters History Matcher Router Components posva
Thank you! posva
When we develop a Single Page Application, we have to use a Router. It’s a common use case, yet every single framework has its own router, React even has multiple ones you can choose from. And even though each framework is different and every router takes a different approach, they all share the same principles. Understanding those helps us handle routing in our SPA because behind all those simple, different APIs provided by framework specific routers, a more complex architecture is hidden from us. Let’s talk about the different types of routers out there and what makes them different and understand if we really need a router for every front end framework by using Vue Router implementation as an example.