Server-Side View Tracking in Astro
--
Track page views server-side in Astro API routes. No client-side JavaScript needed.
import type { APIRoute } from 'astro';import { supabase } from '../../../lib/supabase';
export const POST: APIRoute = async ({ params, request }) => { const { slug } = params;
if (!slug) { return new Response(JSON.stringify({ error: 'Missing slug' }), { status: 400, }); }
// Get IP for unique view detection (optional) const ip = request.headers.get('x-forwarded-for') || 'unknown';
try { // Increment view count const { data, error } = await supabase.rpc('increment_views', { post_slug: slug, visitor_ip: ip, });
if (error) throw error;
return new Response(JSON.stringify({ views: data }), { status: 200, headers: { 'Content-Type': 'application/json', }, }); } catch (error) { console.error('View tracking error:', error); return new Response(JSON.stringify({ error: 'Failed to track view' }), { status: 500, }); }};
export const GET: APIRoute = async ({ params }) => { const { slug } = params;
const { data, error } = await supabase .from('post_stats') .select('views') .eq('slug', slug) .single();
if (error) { return new Response(JSON.stringify({ views: 0 }), { status: 200 }); }
return new Response(JSON.stringify({ views: data.views }), { status: 200, headers: { 'Content-Type': 'application/json' }, });};Client-side usage:
---const { slug } = Astro.props;---
<span id="view-count">Loading...</span>
<script define:vars={{ slug }}> // Track view on page load fetch(`/api/views/${slug}`, { method: 'POST' }) .then(res => res.json()) .then(data => { document.getElementById('view-count').textContent = `${data.views} views`; });</script>Supabase function:
CREATE OR REPLACE FUNCTION increment_views(post_slug TEXT, visitor_ip TEXT)RETURNS INTEGER AS $$DECLARE view_count INTEGER;BEGIN INSERT INTO post_stats (slug, views, last_viewed_ip) VALUES (post_slug, 1, visitor_ip) ON CONFLICT (slug) DO UPDATE SET views = post_stats.views + 1, last_viewed_ip = visitor_ip;
SELECT views INTO view_count FROM post_stats WHERE slug = post_slug; RETURN view_count;END;$$ LANGUAGE plpgsql;Benefits:
- Server-side = accurate (no ad blockers)
- Type-safe with Astro’s
APIRoute - Easy to add rate limiting
- Works without JavaScript