Choose Theme

Server-Side View Tracking in Astro

· 3 min read · #Astro #TypeScript
--

Track page views server-side in Astro API routes. No client-side JavaScript needed.

src/pages/api/views/[slug].ts
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:

src/components/ViewCounter.astro
---
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

Related