Fix: Astro.clientAddress Not Available in Current Adapter

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

What Causes This Error?

This error occurs when you try to access Astro.clientAddress but the configured adapter doesn’t support retrieving the client’s IP address. Not all deployment platforms expose this information.

The Problem

---
// ❌ Adapter doesn't support clientAddress
const ip = Astro.clientAddress;
---

<p>Your IP: {ip}</p>

The Fix

Use Adapter That Supports It

// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'server',
  adapter: vercel(), // ✅ Vercel supports clientAddress
});

Check Before Accessing

---
// ✅ Safely access with fallback
const ip = Astro.clientAddress || 'unknown';
---

<p>Your IP: {ip}</p>

Common Scenarios

Adapters with clientAddress Support

// ✅ Supported
@astrojs/vercel/serverless
@astrojs/netlify
@astrojs/cloudflare
@astrojs/node (with proper proxy config)

// ❌ May not support
@astrojs/vercel/static
Custom adapters without implementation

Node.js Adapter with Proxy

// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'server',
  adapter: node({
    mode: 'standalone',
  }),
});
---
// When behind proxy (nginx, cloudflare), may need header
const ip = Astro.clientAddress ||
  Astro.request.headers.get('x-forwarded-for')?.split(',')[0] ||
  'unknown';
---

Using Request Headers Instead

---
// Alternative: read from headers directly
const forwardedFor = Astro.request.headers.get('x-forwarded-for');
const realIp = Astro.request.headers.get('x-real-ip');
const cfConnectingIp = Astro.request.headers.get('cf-connecting-ip');

const clientIp = cfConnectingIp || realIp || forwardedFor?.split(',')[0] || 'unknown';
---

<p>Your IP: {clientIp}</p>

Cloudflare Workers

---
// Cloudflare provides IP via header
const ip = Astro.request.headers.get('cf-connecting-ip') || Astro.clientAddress;
---

Development vs Production

---
// clientAddress may be different in dev vs prod
const ip = import.meta.env.DEV
  ? '127.0.0.1' // Dev fallback
  : Astro.clientAddress || 'unknown';
---

Geolocation from IP

---
const ip = Astro.clientAddress;

// If you need geo data, use a service
// Vercel provides geo headers automatically
const country = Astro.request.headers.get('x-vercel-ip-country');
const city = Astro.request.headers.get('x-vercel-ip-city');
---

<p>Location: {city}, {country}</p>

Rate Limiting Use Case

---
// For rate limiting, handle missing IP gracefully
const ip = Astro.clientAddress || Astro.request.headers.get('x-forwarded-for')?.split(',')[0];

if (!ip) {
  // Can't rate limit without IP
  console.warn('No client IP available');
}
---

Alternative: Use Middleware

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware((context, next) => {
  // Try multiple sources
  const ip =
    context.clientAddress ||
    context.request.headers.get('x-forwarded-for')?.split(',')[0] ||
    context.request.headers.get('x-real-ip') ||
    'unknown';

  context.locals.clientIp = ip;
  return next();
});
---
const ip = Astro.locals.clientIp;
---

Quick Checklist

  • Check if adapter supports clientAddress
  • Use fallback: Astro.clientAddress || 'unknown'
  • Try request headers as alternative
  • Different headers for different platforms (CF, Vercel, etc.)
  • Handle gracefully in development mode