{"id":200,"date":"2020-05-28T00:08:27","date_gmt":"2020-05-27T21:08:27","guid":{"rendered":"https:\/\/webcademy.ru\/blog\/?p=200"},"modified":"2020-05-29T23:21:46","modified_gmt":"2020-05-29T20:21:46","slug":"build-a-very-basic-spa-javascript-router","status":"publish","type":"post","link":"https:\/\/webcademy.ru\/blog\/200\/","title":{"rendered":"Build a very basic SPA JavaScript router"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Simple Plain JavaScript Router<\/h2>\n\n\n\n<p>In this post I&#8217;ll implement an extreme basic SPA routing using plain JavaScript.<br>The goal is to give an idea of how it&#8217;s possible to render different dynamic contents based on the URL with plan JavaScript.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Requirements<\/h4>\n\n\n\n<p>We want to have a basic website that shows different topic based on 3 urls:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"http:\/\/localhost:3000\/\">http:\/\/localhost:3000\/<\/a><\/li><li><a href=\"http:\/\/localhost:3000\/#\/page1\">http:\/\/localhost:3000\/#\/page1<\/a><\/li><li><a href=\"http:\/\/localhost:3000\/#\/page2\">http:\/\/localhost:3000\/#\/page2<\/a><\/li><\/ul>\n\n\n\n<p>For other urls we show an error message.<br>We can use HTML and plain JavaScript.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Setup<\/h4>\n\n\n\n<p>Let&#8217;s create the HTML page&nbsp;<code>index.html<\/code><\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\">&lt;html&gt;\n  &lt;head&gt;\n    &lt;title&gt;JavaScript Router Example&lt;\/title&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;header&gt;\n      &lt;h1&gt;JavaScript Router Example&lt;\/h1&gt;\n    &lt;\/header&gt;\n    &lt;section id=&quot;app&quot;&gt;&lt;\/section&gt;\n    &lt;nav&gt;\n      &lt;a href=&quot;\/&quot;&gt;Home&lt;\/a&gt; -\n      &lt;a href=&quot;#\/page1&quot;&gt;Page 1&lt;\/a&gt; -\n      &lt;a href=&quot;#\/page2&quot;&gt;Page 2&lt;\/a&gt;\n    &lt;\/nav&gt;\n    &lt;script type=&quot;text\/javascript&quot; src=&quot;.\/app.js&quot; \/&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;<\/pre><\/div>\n\n\n\n<p>and an empty JS file&nbsp;<code>app.js<\/code>.<\/p>\n\n\n\n<p>In order to serve it, we can install&nbsp;<code>live-server<\/code>&nbsp;globally:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\">npm install -g live-server<\/pre><\/div>\n\n\n\n<p>and then run it on our HTML file:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\">live-server --port=3000 --entry-file=\u2019.\/index.html\u2019<\/pre><\/div>\n\n\n\n<p>Now it should be possibile to visit&nbsp;<code>http:\/\/localhost:3000\/<\/code>&nbsp;and see the page.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Create the components<\/h3>\n\n\n\n<p>Let&#8217;s create the components now.<\/p>\n\n\n\n<p>We use the &#171;template literal&#187; expression, which is a string literal that can span multiple lines and interpolate expressions.<\/p>\n\n\n\n<p>Each component has a&nbsp;<code>render<\/code>&nbsp;method that returns the HTML template.<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;javascript&quot;,&quot;mime&quot;:&quot;text\/javascript&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;JavaScript&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;js&quot;}\">\/\/ Components\nconst HomeComponent = {\n  render: () =&gt; {\n    return `\n      &lt;section&gt;\n        &lt;h1&gt;Home&lt;\/h1&gt;\n        &lt;p&gt;This is just a test&lt;\/p&gt;\n      &lt;\/section&gt;\n    `;\n  }\n} \n\nconst Page1Component = {\n  render: () =&gt; {\n    return `\n      &lt;section&gt;\n        &lt;h1&gt;Page 1&lt;\/h1&gt;\n        &lt;p&gt;This is just a test&lt;\/p&gt;\n      &lt;\/section&gt;\n    `;\n  }\n} \n\nconst Page2Component = {\n  render: () =&gt; {\n    return `\n      &lt;section&gt;\n        &lt;h1&gt;Page 2&lt;\/h1&gt;\n        &lt;p&gt;This is just a test&lt;\/p&gt;\n      &lt;\/section&gt;\n    `;\n  }\n} \n\nconst ErrorComponent = {\n  render: () =&gt; {\n    return `\n      &lt;section&gt;\n        &lt;h1&gt;Error&lt;\/h1&gt;\n        &lt;p&gt;This is just a test&lt;\/p&gt;\n      &lt;\/section&gt;\n    `;\n  }\n}<\/pre><\/div>\n\n\n\n<p>Now we have the components that we want to show in the page.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create the routes<\/h2>\n\n\n\n<p>We need to create the routes and connect them somehow with the components.<\/p>\n\n\n\n<p>So, let&#8217;s do it in a easy way:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;javascript&quot;,&quot;mime&quot;:&quot;text\/javascript&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;JavaScript&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;js&quot;}\">\/\/ Routes \nconst routes = [\n  { path: '\/', component: HomeComponent, },\n  { path: '\/page1', component: Page1Component, },\n  { path: '\/page2', component: Page2Component, },\n];<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Router<\/h2>\n\n\n\n<p>How should the router look like?<br>Let&#8217;s assume that our goal is to code something like that:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;javascript&quot;,&quot;mime&quot;:&quot;text\/javascript&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;JavaScript&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;js&quot;}\">const router = () =&gt; {\n  \/\/ TODO: Get the current path\n  \/\/ TODO: Find the component based on the current path\n  \/\/ TODO: If there's no matching route, get the &quot;Error&quot; component\n  \/\/ TODO: Render the component in the &quot;app&quot; placeholder\n};<\/pre><\/div>\n\n\n\n<p>Then let&#8217;s start! \ud83d\ude42 <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Get the current path<\/h3>\n\n\n\n<p>The&nbsp;<code>location<\/code>&nbsp;object is excatly the tool we need.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>The Location interface represents the location (URL) of the object it is linked to. Changes done on it are reflected on the object it relates to. Both the Document and Window interface have such a linked Location, accessible via Document.location and Window.location respectively. (<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Location\">MDN Web Docs<\/a>)<\/p><\/blockquote>\n\n\n\n<p>One property of the location object is&nbsp;<code>location.hash<\/code>, which contains the part of the URL from &#8216;#&#8217; followed by the fragment identifier of the URL.<\/p>\n\n\n\n<p>In other words, given this URL:&nbsp;<code>http:\/\/foo.bar\/#\/hello<\/code>, location.hash would be: &#8216;#\/hello&#8217;.<\/p>\n\n\n\n<p>So we need to extract from that string something we can use with out&nbsp;<code>routes<\/code>.<\/p>\n\n\n\n<p>We remove the &#171;#&#187; char from and in case any hash value is provided, we assume it will be the base url:&nbsp;<code>\/<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\">const parseLocation = () =&gt; location.hash.slice(1).toLowerCase() || '\/';<\/pre><\/div>\n\n\n\n<p>At this point we solved the first &#171;TODO&#187; of the list:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\">const router = () =&gt; {\n  \/\/  Find the component based on the current path\n  const path = parseLocation();\n  \/\/ TODO: If there's no matching route, get the &quot;Error&quot; component\n  \/\/ TODO: Render the component in the &quot;app&quot; placeholder\n};<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Get the the right component<\/h3>\n\n\n\n<p>Since we have the&nbsp;<code>path<\/code>, what we need to do is to get the first matching entry of the&nbsp;<code>routes<\/code>.<\/p>\n\n\n\n<p>In case we can&#8217;t find any route, that we just return&nbsp;<code>undefined<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\">const findComponentByPath = (path, routes) =&gt; routes.find(r =&gt; r.path.match(new RegExp(`^\\\\${path}$`, 'gm'))) || undefined;<\/pre><\/div>\n\n\n\n<p>We solve the next TODO now!<br>We use a &#171;destructuring assignment&#187; to assign the matching component to the const&nbsp;<code>component<\/code>, which gets by default the&nbsp;<code>ErrorComponent<\/code>.<br>Since the &#171;destructuring assignment&#187; requires an object on the right-hand side and since our&nbsp;<code>findComponentByPath<\/code>&nbsp;function could return&nbsp;<code>undefined<\/code>, we provide in this case just an empty object&nbsp;<code>{}<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\">const router = () =&gt; {\n  \/\/ Find the component based on the current path\n  const path = parseLocation();\n  \/\/ If there's no matching route, get the &quot;Error&quot; component\n  const { component = ErrorComponent } = findComponentByPath(path, routes) || {};\n  \/\/ TODO: Render the component in the &quot;app&quot; placeholder\n};<\/pre><\/div>\n\n\n\n<p>Now we are ready to solve the third and last TODO: render the component in the app.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Render the component<\/h3>\n\n\n\n<p>If you remember, our components have a&nbsp;<code>render<\/code>&nbsp;method that returns the HTML template.<br>So we have to put that template in the app&nbsp;<code>&lt;section id=\"app\"&gt;&lt;\/section&gt;<\/code>.<\/p>\n\n\n\n<p>This is very easy, you know.<br>We get the element using the id and put the content in the&nbsp;<code>innerHTML<\/code>&nbsp;property.<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\">document.getElementById('app').innerHTML = component.render();<\/pre><\/div>\n\n\n\n<p>The&nbsp;<code>router<\/code>&nbsp;is ready:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\">const router = () =&gt; {\n  \/\/ Find the component based on the current path\n  const path = parseLocation();\n  \/\/ If there's no matching route, get the &quot;Error&quot; component\n  const { component = ErrorComponent } = findComponentByPath(path, routes) || {};\n  \/\/ Render the component in the &quot;app&quot; placeholder\n  document.getElementById('app').innerHTML = component.render();\n};<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Make it work<\/h3>\n\n\n\n<p>Even if the code would work, there&#8217;s something sill missing.<br>We are never calling the&nbsp;<code>router<\/code>! Our code right hasn&#8217;t been executed yet.<\/p>\n\n\n\n<p>We need to call it in 2 cases:<br>1) On page load since we want to show the right content from the very first moment<br>2) On every location update (actually every &#171;hash&#187; location update)<\/p>\n\n\n\n<p>We need to add to event listeners and bind them with our&nbsp;<code>router<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:false,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\">window.addEventListener('hashchange', router);\nwindow.addEventListener('load', router);<\/pre><\/div>\n\n\n\n<p>That&#8217;s it \ud83d\ude42<\/p>\n\n\n\n<p>Here you can find a live example:<\/p>\n\n\n\n<iframe\n     src=\"https:\/\/codesandbox.io\/embed\/wispy-flower-jwlwc?fontsize=14&#038;hidenavigation=1&#038;theme=dark\"\n     style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\"\n     title=\"wispy-flower-jwlwc\"\n     allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n     sandbox=\"allow-autoplay allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n   ><\/iframe>\n\n\n\n<h1 class=\"wp-block-heading\">Key takeaway points:<\/h1>\n\n\n\n<p>\u2022 Learn how Window.location works<br>\u2022 Learn how template literals work<br>\u2022 Learn how EventTarget.addEventListener() works<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Docs:<\/h1>\n\n\n\n<p>\u2022&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/location\">Window.location<\/a><br>\u2022&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Template_literals\">Template literals (Template strings)<\/a><br>\u2022&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/EventTarget\/addEventListener\">EventTarget.addEventListener()<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">About this post<\/h2>\n\n\n\n<p>I&#8217;m am running a&nbsp;<strong>free<\/strong>&nbsp;<strong>JavaScript Learning Group<\/strong>&nbsp;on&nbsp;<a href=\"https:\/\/pixari.slack.com\/\">pixari.slack.com<\/a>&nbsp;and I use this blog as official blog of the community.<br>I pick some of the questions from the #questions-answer channel and answer via blog post. This way my answers will stay indefinitely visible for everyone.&#187;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Simple Plain JavaScript Router In this post I&#8217;ll implement an extreme basic SPA routing using plain JavaScript.The goal is to give an idea of how it&#8217;s possible to render different dynamic contents based on the URL with plan JavaScript. Requirements We want to have a basic website that shows different topic based on 3 urls: [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":201,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[11,13,12],"class_list":["post-200","post","type-post","status-publish","format-standard","has-post-thumbnail","","category-javascript","tag-javascript","tag-router","tag-spa"],"_links":{"self":[{"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/posts\/200"}],"collection":[{"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/comments?post=200"}],"version-history":[{"count":1,"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/posts\/200\/revisions"}],"predecessor-version":[{"id":235,"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/posts\/200\/revisions\/235"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/media\/201"}],"wp:attachment":[{"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/media?parent=200"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/categories?post=200"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/webcademy.ru\/blog\/wp-json\/wp\/v2\/tags?post=200"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}