@@ -142,6 +142,23 @@ export function initRouter() {
142142/**
143143 * Handle route change
144144 */
145+ // SPA pageview tracking for GoatCounter. count.js auto-counts the initial full
146+ // page load, so the first handleRoute() is skipped to avoid a double hit; every
147+ // later client-side route change — including each opened anchor (/anchor/:id) —
148+ // is reported with its resolved path and title, giving per-anchor view counts.
149+ // Privacy is unchanged: path only, no query string, no personal data.
150+ let firstRouteHandled = false
151+ function trackPageview ( ) {
152+ if ( ! firstRouteHandled ) {
153+ firstRouteHandled = true
154+ return
155+ }
156+ const gc = typeof window !== 'undefined' ? window . goatcounter : null
157+ if ( gc && typeof gc . count === 'function' ) {
158+ gc . count ( { path : window . location . pathname , title : document . title } )
159+ }
160+ }
161+
145162function handleRoute ( ) {
146163 let path = getCurrentRoute ( )
147164
@@ -176,6 +193,7 @@ function handleRoute() {
176193 // Set title to anchor name
177194 const readableName = safeAnchorId . replace ( / - / g, ' ' ) . replace ( / \b \w / g, ( c ) => c . toUpperCase ( ) )
178195 document . title = `${ readableName } — Semantic Anchors`
196+ trackPageview ( )
179197
180198 // Open the anchor modal as overlay on current page
181199 import ( '../components/anchor-modal.js' ) . then ( ( { showAnchorDetails } ) => {
@@ -190,6 +208,7 @@ function handleRoute() {
190208 currentRoute = path
191209 document . title = ROUTE_TITLES [ path ] || 'Semantic Anchors'
192210 handler ( )
211+ trackPageview ( )
193212 } else {
194213 // Default to home if route not found
195214 const homeHandler = routes . get ( '/' )
0 commit comments