I’m getting started with Svelte and building a single-page application (using page.js as the router). I thought I’d have a separate component to produce the <svelte:head>
block, and when each component mounts it would write the page title to a store, which would then be read by the head component. It partially works – the page title is updated when I click through to different pages. However, if I go back in my browser’s history, the title doesn’t change back with the page. It does change if I then reload the page. So perhaps onMount()
isn’t the right lifecycle method. What approach can I take that will work with history state navigation?
Here’s my app boiled down to a minimal example.
// index.js import page from 'page' import App from './views/App.svelte' const app = new App({ target: document.body, props: { route: null, }, }) function one() { app.$set({ route: 'one' }) } function two() { app.$set({ route: 'two' }) } page('/one', one) page('/two', two) page() // App.svelte <script> import One from './One.svelte' import Two from './Two.svelte' import Head from '../parts/Head.svelte' import Home from './Home.svelte' export let route </script> <Head /> {#if route === 'one'} <One /> {:else if route === 'two'} <Two /> {:else} <Home /> {/if} // Head.svelte <script> import { pageName } from '../stores.js' let displayPageName pageName.subscribe(value => { displayPageName = value }) </script> <svelte:head> {#if displayPageName} <title>Test App — {displayPageName}</title> {:else} <title>Test App</title> {/if} </svelte:head> // stores.js import { writable } from 'svelte/store' export const pageName = writable(null) // Home.svelte <a href="/one">One</a> <a href="/two">Two</a> // One.svelte <script> import { onMount } from 'svelte' import { pageName } from '../stores.js' onMount(async () => { pageName.update(() => 'Component One') }) </script> <a href="/two">Two</a> // Two.svelte <script> import { onMount } from 'svelte' import { pageName } from '../stores.js' onMount(async () => { pageName.update(() => 'Component Two') }) </script> <a href="/one">One</a>
Answer
For some reasons, Svelte doesn’t seem to like the <title>
in an {#if}
block… I recommend computing the value in a reactive statement instead.
$: title = $pageName ? `Test App u2014 ${$pageName}` : 'Test App'
$pageName
is a special syntax to access the store’s value without having to handle subscribe/unsubscribe yourself (docs).
Even with that, I found that my browser (tested in Chrome) was apparently OK with ignoring the <title>
in the DOM when navigating back. We can force the value of document.title
to workaround that. Another reactive block can take care of that:
$: { document.title = title }
So your whole Head.svelte
component would now looks like this:
<script> import { pageName } from '../stores.js' $: title = $pageName ? `Test App u2014 ${$pageName}` : 'Test App' $: { document.title = title } </script> <svelte:head> <title>{title}</title> </svelte:head>
And finally, in your example, you’re not updating the store’s value when you navigate to /
, so you’d have a stale title in this case. I think you need to add a route like so:
page('/', () => app.$set({ route: null }))