Code

js/router.js — the route table and hashchange

In the code column — js/router.js. Three parts: the table (routes Map), parseRoute (hash → a route object) and handleRoute (runs the matching function).

parseRoute splits the path and returns a route with params:

if (parts[0] === 'apod' && parts[1]) return { name: 'detail', date: parts[1] };

#/apod/2026-07-11{ name: 'detail', date: '2026-07-11' }. The date is a route param, and it's exactly what enables deep links.

navigate(path) just sets location.hash — which itself fires hashchange. We don't call handleRoute directly; we let the event tie it together. So both a link and Back go through the same path.

Notice the path-changed guard:

if (path === lastPath) return;     // same place? don't re-render
lastPath = path;

Why? Because search writes ?q=… into the URL (the next step), which also fires hashchange. But the path (/gallery) doesn't change — only the query. Without this guard, every search keystroke would re-run the whole view render and jump to the top. We react only to path changes.

Routes are bound to views (already written in earlier lessons):

onRoute('home', renderHome);
onRoute('gallery', () => { /* restore search from the URL, then */ renderGallery(); });
onRoute('detail', (route) => renderDetail(route));    // route.date → deep link

home.js (the landing page with stats) and detail.js (one picture) are the last views, arriving here because their whole reason to exist is to be routes.

Tip. renderDetail(route) gets route.date. If the picture isn't in memory yet (you arrived via a deep link), detail.js fetches it itself (fetchPicture(date)), then via addPicturenotify() it re-renders. The deep link "works from nowhere" — because the view knows how to load itself.