single-page app tricks without a framework


so, let's say you're me, and your blog has a dark theme and a light theme. you want people to be able to switch between the light theme and dark theme outside of their browser's default, and you want that selection to persist between pages. so, you save the selection to localStorage and load it in when the new page loads. problem solved!

…except now the theme flickers horribly every time you switch pages, because it first loads in as the browser's default. of course. and if the user presses the back button, their selection gets all manner of messed up, since the browser is loading the old page from memory, which hasn't had the theme switched. can we fix this?

yep! by turning your simple POSH website into an SPA using this shiny new framework, you can…

oh, huh. it seems that nobody else has actually just written a few lines of JS to do this on their own. it can't be that hard is it? all we need to do is swap out the entire webpage on the fly!

it'll be fine. trust me.

anyways, here's the code:

spa-tricks.js
async function switchPage(state, url) {
	let body, title, scroll
	if (!state) {
		try {
			let response = await fetch(url)
			if (!response.ok) {
				throw new Error(`Error: ${response.status}`)
			}
			let result = await response.text()
			body = result.match(/(?>=(>body>))(.*)(?=(<\/body>))/s)[0]
			title = result.match(/(?>=(>title>))(.*)(?=(<\/title>))/s)[0]
		} catch (error) {
			console.error(error.message)
		}
		scroll = {x: window.scrollX, y: window.scrollY}
		window.history.pushState({url, body, title, scroll}, "", url)
		scroll = {x: 0, y: 0}
	} else {
		body = state.body
		title = state.title
		url = state.url
		scroll = state.scroll
	}
	document.body.innerHTML = body
	document.title = title
	window.scroll(scroll.x, scroll.y)
}

document.onclick = function (e) {
	e = e || window.event
	var element = e.target || e.srcElement
	if (element.tagName == "IMG") {
		if (element.parentElement.tagName == "A") {
			element = element.parentElement
		}
	}
	if (!element.classList.contains("no-tricks")){
		if (element.tagName == "A") {
			if (element.host == window.location.host) {
				if (element.hash) {
					document.getElementById(element.hash.slice(1)).scrollIntoView()
				} else {
					switchPage("", element.href)
				}
				return false
			}
		}
	}
}

window.addEventListener("popstate", (e) => {
	if (e.state){
		switchPage(e.state, "")
	} else {
		switchPage("", e.target.location.href)
	}
})

the main trick is using a bit of regex to separate out the parts of the HTML we care about, so that we can slap it right into the current page. then, we push everything we care about to the window history stack so your back button still works. now our theme switcher doesn't flicker between pages!

of course, the code i'm showing off here has one big caveat; which is that it only changes the body, not the head. this is fine for my website, since all of my pages have pretty much identical stuff up there, anyways. if that's not what your website is like, sorry; this evil trick won't work for you.