Chapter 160 · Omnibus Instrument Integration
Subchapter 160.10
references/EXAMPLE-astro-view-transitions.mdMarkdown23 KBView on GitHub
Repository: https://github.com/PostHog/context-mill Path: basics/astro-view-transitions
This is an Astro (opens in a new tab) example demonstrating PostHog integration with View Transitions (opens in a new tab) (ClientRouter) for SPA-like navigation.
It uses the PostHog web snippet with special handling to prevent stack overflow errors during soft navigation, and shows how to:
posthog.captureException()<ClientRouter />capture_pageview: 'history_change' for soft navigationnpm install
# or
pnpm installCreate a .env file in the project root:
PUBLIC_POSTHOG_PROJECT_TOKEN=your_posthog_project_token
PUBLIC_POSTHOG_HOST=https://us.i.posthog.comGet your PostHog project token from your project settings in PostHog.
npm run dev
# or
pnpm devOpen http://localhost:4321 in your browser.
src/
components/
posthog.astro # PostHog snippet WITH initialization guard
Header.astro # Navigation + logout, uses astro:page-load event
layouts/
PostHogLayout.astro # Root layout with <ClientRouter /> and PostHog
lib/
auth.ts # Auth utilities (localStorage-based)
pages/
index.astro # Login form, identifies user + captures 'user_logged_in'
burrito.astro # Burrito consideration demo, captures 'burrito_considered'
profile.astro # Profile + error tracking demo
styles/
global.css # Global styles + view transition animationsWhen using Astro’s View Transitions (ClientRouter), you must wrap the PostHog initialization with a guard to prevent stack overflow errors:
<script is:inline>
// IMPORTANT: Guard against multiple initializations during view transitions
if (!window.__posthog_initialized) {
window.__posthog_initialized = true;
!function(t,e){...}(document,window.posthog||[]);
posthog.init('<ph_project_token>', {
api_host: 'https://us.i.posthog.com',
defaults: '2026-01-30',
// IMPORTANT: Use 'history_change' for automatic pageview tracking during soft navigation
capture_pageview: 'history_change'
})
}
</script>Without this guard, ClientRouter’s soft navigation can re-execute the inline script during page transitions, causing a stack overflow error.
The capture_pageview: 'history_change' option ensures pageviews are tracked automatically as users navigate between pages.
The layout includes Astro’s ClientRouter for smooth page transitions:
---
import { ClientRouter } from 'astro:transitions';
import PostHog from '../components/posthog.astro';
---
<html>
<head>
<ClientRouter />
<PostHog />
</head>
...
</html>When using View Transitions, you need to set up event listeners after each page navigation:
function setupPage() {
// Your setup code here
}
// Run on initial page load
document.addEventListener("DOMContentLoaded", setupPage);
// Run after view transitions complete (for soft navigation)
document.addEventListener("astro:page-load", setupPage);After a successful “login”, the app identifies the user and captures a login event:
window.posthog?.identify(username);
window.posthog?.capture("user_logged_in");The burrito page tracks a custom event when a user “considers” the burrito:
window.posthog?.capture("burrito_considered", {
total_considerations: newCount,
username: currentUser,
});On logout, both the local auth state and PostHog state are cleared:
window.posthog?.capture("user_logged_out");
localStorage.removeItem("currentUser");
window.posthog?.reset();# Run dev server
npm run dev
# Build for production
npm run build
# Preview production build
npm run previewPUBLIC_POSTHOG_PROJECT_TOKEN=your_posthog_project_token_here
PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
import { defineConfig } from "astro/config";
export default defineConfig({});
---
// Header component with navigation and logout functionality
// Works with View Transitions by using data-astro-reload for logout
---
<header class="header">
<div class="header-container">
<nav>
<a href="/">Home</a>
<a href="/burrito" class="auth-link" style="display: none;"
---
// PostHog analytics snippet with View Transitions support
// Uses is:inline to prevent Astro from processing the script
// Includes initialization guard to prevent stack overflow with ClientRouter
---
<script is:inline define:vars={{ apiKey: import.meta.env.PUBLIC_POSTHOG_PROJECT_TOKEN, apiHost: import.meta.env.PUBLIC_POSTHOG_HOST }}>
// IMPORTANT: Guard against multiple initializations during view transitions
// Without this guard, ClientRouter's soft navigation can re-execute the inline script
// during page transitions, causing a stack overflow error.
if (!window.__posthog_initialized) {
window.__posthog_initialized = true;
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=
posthog.init(apiKey || '', {
api_host: apiHost || 'https://us.i.posthog.com',
defaults: '2026-01-30',
// IMPORTANT: Use 'history_change' to automatically track pageviews during soft navigation
capture_pageview: 'history_change'
})
}
</script>
---
import { ClientRouter } from 'astro:transitions';
import PostHog from '../components/posthog.astro';
import Header from '../components/Header.astro';
import '../styles/global.css';
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Astro PostHog Integration with View Transitions" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
<ClientRouter />
<PostHog />
</head>
<body>
<Header />
<main>
<slot />
</main>
</body>
</html>
// Client-side auth utilities for localStorage-based authentication
export interface User {
username: string;
burritoConsiderations: number;
}
export function getCurrentUser(): User | null {
if (typeof window === "undefined") return null;
const username = localStorage.getItem("currentUser");
if (!username) return null;
const considerations = parseInt(
localStorage.getItem("burritoConsiderations") || "0",
10,
);
return {
username,
burritoConsiderations: considerations,
};
}
export function login(username: string, password: string): boolean {
if (!username || !password) return false;
localStorage.setItem("currentUser", username);
// Initialize burrito considerations if not set
if (!localStorage.getItem("burritoConsiderations")) {
localStorage.setItem("burritoConsiderations", "0");
}
return true;
}
export function logout(): void {
localStorage.removeItem("currentUser");
localStorage.removeItem("burritoConsiderations");
}
export function incrementBurritoConsiderations(): number {
const current = parseInt(
localStorage.getItem("burritoConsiderations") || "0",
10,
);
const newCount = current + 1;
localStorage.setItem("burritoConsiderations", newCount.toString());
return newCount;
}
---
import PostHogLayout from '../layouts/PostHogLayout.astro';
---
<PostHogLayout title="Burrito Consideration - Astro PostHog with View Transitions">
<div class="container">
<h1>Burrito consideration zone</h1>
<p>Take a moment to truly consider the potential of burritos.</p>
<div style="text-align: center;">
<button id="consider-btn" class="btn-burrito">
I have considered the burrito potential
</button>
<p id="success-message" class="success" style="display: none;">
Thank you for your consideration! Count: <span id="consideration-count"></span>
</p>
</div>
<div class="stats">
<h3>Consideration stats</h3>
<p>Total considerations: <span id="total-considerations">0</span></p>
</div>
</div>
</PostHogLayout>
<script is:inline>
function checkAuth() {
const currentUser = localStorage.getItem('currentUser');
if (!currentUser) {
window.location.href = '/';
return false;
}
return true;
}
function updateStats() {
const count = localStorage.getItem('burritoConsiderations') || '0';
const totalElement = document.getElementById('total-considerations');
if (totalElement) {
totalElement.textContent = count;
}
}
function handleConsideration() {
const currentUser = localStorage.getItem('currentUser');
if (!currentUser) return;
// Increment the count
const currentCount = parseInt(localStorage.getItem('burritoConsiderations') || '0', 10);
const newCount = currentCount + 1;
localStorage.setItem('burritoConsiderations', newCount.toString());
// Update the UI
updateStats();
const successMessage = document.getElementById('success-message');
const considerationCount = document.getElementById('consideration-count');
if (considerationCount) {
considerationCount.textContent = newCount;
}
if (successMessage) {
successMessage.style.display = 'block';
// Hide success message after 2 seconds
setTimeout(() => {
successMessage.style.display = 'none';
}, 2000);
}
// Capture burrito consideration event in PostHog
window.posthog?.capture('burrito_considered', {
total_considerations: newCount,
username: currentUser
});
}
function setupBurritoPage() {
if (!checkAuth()) return;
updateStats();
const btn = document.getElementById('consider-btn');
// Remove existing listener to prevent duplicates during view transitions
btn?.removeEventListener('click', handleConsideration);
btn?.addEventListener('click', handleConsideration);
}
// Run on initial page load
document.addEventListener('DOMContentLoaded', setupBurritoPage);
// Run after view transitions complete (for soft navigation)
document.addEventListener('astro:page-load', setupBurritoPage);
</script>
---
import PostHogLayout from '../layouts/PostHogLayout.astro';
---
<PostHogLayout title="Home - Astro PostHog with View Transitions">
<div class="container">
<div id="logged-in-view" style="display: none;">
<h1>Welcome back, <span id="welcome-username"></span>!</h1>
---
import PostHogLayout from '../layouts/PostHogLayout.astro';
---
<PostHogLayout title="Profile - Astro PostHog with View Transitions">
<div class="container">
<h1>User Profile</h1>
<div class="stats">
<h2>Your Information</h2>
<p><