music on atproto
plyr.fm
1# client-side navigation
2
3## preserving player state across navigation
4
5the player lives in the root layout (`+layout.svelte`) and persists across all page navigations. to maintain uninterrupted playback, client-side navigation must work correctly.
6
7## critical rule: never use `stopPropagation()` on links
8
9**problem**: calling `e.stopPropagation()` on link click handlers breaks SvelteKit's client-side router, causing full page reloads that unmount and remount the player.
10
11```svelte
12<!-- ❌ WRONG - causes full page reload, interrupts playback -->
13<a href="/tag/{tag}" onclick={(e) => e.stopPropagation()}>{tag}</a>
14```
15
16```svelte
17<!-- ✅ CORRECT - client-side navigation works, playback continues -->
18<a href="/tag/{tag}">{tag}</a>
19```
20
21## handling links inside clickable containers
22
23when you have links nested inside a clickable button/div, check the event target instead of using `stopPropagation()`:
24
25```svelte
26<button
27 onclick={(e) => {
28 // skip if user clicked a link inside
29 if (e.target instanceof HTMLAnchorElement || (e.target as HTMLElement).closest('a')) {
30 return;
31 }
32 doSomething();
33 }}
34>
35 <span>click me</span>
36 <a href="/other-page">or click this link</a>
37</button>
38```
39
40this pattern:
411. lets the link trigger proper client-side navigation
422. only calls `doSomething()` when clicking non-link elements
433. preserves all global state including the player
44
45## why this matters
46
47SvelteKit's client-side router intercepts `<a>` tag clicks to:
48- avoid full page reload
49- preserve global state (player, queue, auth)
50- enable smooth transitions
51
52when `stopPropagation()` is called, the click event never reaches SvelteKit's router, falling back to native browser navigation which:
53- performs a full page reload
54- unmounts and remounts all components
55- resets audio playback
56
57## examples from the codebase
58
59### TrackItem.svelte
60
61the track container is a button that plays the track on click. it contains multiple links (artist, album, tags) that should navigate without affecting playback:
62
63```svelte
64<button
65 class="track"
66 onclick={(e) => {
67 // only play if clicking the track itself, not a link inside
68 if (e.target instanceof HTMLAnchorElement || (e.target as HTMLElement).closest('a')) {
69 return;
70 }
71 onPlay(track);
72 }}
73>
74 <a href="/u/{track.artist_handle}" class="artist-link">{track.artist}</a>
75 <a href="/tag/{tag}" class="tag-badge">{tag}</a>
76</button>
77```
78
79## debugging navigation issues
80
81**symptom**: clicking a link stops music playback
82
83**diagnosis**:
841. check if the link has `onclick={(e) => e.stopPropagation()}`
852. check parent elements for event handling that might interfere
863. verify the route uses SvelteKit conventions (`+page.svelte`, `+page.ts`)
87
88**fix**: remove `stopPropagation()` and use event target checking in parent handlers instead