Fix: Astro.clientAddress Not Available in Prerendered Pages

Error message:
`Astro.clientAddress` is not available in prerendered pages.
Adapters & SSR 2025-01-25

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

  • clientAddress only works on server-rendered pages
  • Add export const prerender = false for 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