What Causes This Error?
This error occurs when you try to access Astro.clientAddress in a page that is prerendered (static). Client IP addresses are only available at request time, not build time.
The Problem
---
// src/pages/index.astro
// Page is prerendered (static) by default
const ip = Astro.clientAddress; // ❌ Not available at build time
---
<p>Your IP: {ip}</p>
The Fix
Disable Prerendering for the Page
---
// ✅ Make page server-rendered
export const prerender = false;
const ip = Astro.clientAddress;
---
<p>Your IP: {ip}</p>
Or Fetch on Client Side
---
// Keep page static, fetch IP on client
---
<p>Your IP: <span id="ip">Loading...</span></p>
<script>
// ✅ Fetch IP from API route
fetch('/api/ip')
.then(res => res.json())
.then(data => {
document.getElementById('ip').textContent = data.ip;
});
</script>
// src/pages/api/ip.ts
export const prerender = false;
export async function GET({ clientAddress }) {
return new Response(JSON.stringify({ ip: clientAddress }), {
headers: { 'Content-Type': 'application/json' },
});
}
Common Scenarios
Understanding Prerendering
---
// Static (prerendered) - built at build time
// No access to: clientAddress, cookies, request headers (dynamic)
// Server-rendered - built at request time
export const prerender = false;
// Has access to: clientAddress, cookies, request headers
---
API Route for IP
// src/pages/api/ip.ts
export const prerender = false;
export async function GET({ clientAddress, request }) {
const ip = clientAddress ||
request.headers.get('x-forwarded-for')?.split(',')[0] ||
'unknown';
return new Response(JSON.stringify({ ip }), {
headers: { 'Content-Type': 'application/json' },
});
}
Hybrid Mode
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'hybrid', // Static by default
adapter: vercel(),
});
---
// src/pages/dashboard.astro
// This specific page needs client info
export const prerender = false;
const ip = Astro.clientAddress;
---
Move Logic to Middleware
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware((context, next) => {
// Only available for non-prerendered routes
if (!context.isPrerendered) {
context.locals.clientIp = context.clientAddress;
}
return next();
});
Client-Side IP Detection
---
// Static page with client-side IP lookup
---
<div id="visitor-info">
<p>IP: <span id="ip">-</span></p>
<p>Location: <span id="location">-</span></p>
</div>
<script>
// Use external service for IP in static pages
fetch('https://api.ipify.org?format=json')
.then(res => res.json())
.then(data => {
document.getElementById('ip').textContent = data.ip;
});
</script>
When to Use Each Approach
Static page + Client-side fetch:
- Marketing pages that show personalized greeting
- Analytics that don't block page load
- Non-critical personalization
Server-rendered page:
- Security-sensitive IP checks
- Rate limiting
- Access control
- Server-side logging
Check if Prerendered
---
// In layouts or components that might be used in both contexts
const isPrerendered = import.meta.env.SSR ? false : true;
let ip = 'unknown';
if (!isPrerendered && Astro.clientAddress) {
ip = Astro.clientAddress;
}
---
Quick Checklist
-
clientAddressonly works on server-rendered pages - Add
export const prerender = falsefor SSR - Use API routes for static pages needing client info
- Consider client-side fetch for non-critical IP data
- Use hybrid mode for mixed static/SSR sites