analytics.tube
Proxy Guide

Astro Proxy Setup

Configure Astro middleware to proxy analytics-tube tracking requests

Astro's middleware feature makes it simple to proxy analytics-tube tracking. This guide shows how to set up server-side request proxying in your Astro application.

Overview

Astro middleware allows you to intercept and modify requests before they're handled, making it perfect for creating a transparent analytics proxy.

What you'll achieve:

  • Proxy analytics-tube endpoints through your Astro site
  • Forward necessary headers for accurate tracking
  • Simple configuration in astro.config.mjs
  • Support all analytics-tube features

Prerequisites

  • Astro 2.0 or later (with SSR or hybrid rendering enabled)
  • Your analytics-tube instance URL:
    • Cloud hosted: https://app.analytics.tube
    • Self-hosted: Your instance URL
  • Your analytics-tube site ID

This guide requires SSR (Server-Side Rendering) or hybrid mode. Static sites cannot proxy requests server-side. For static sites on platforms like Vercel or Netlify, use their platform-specific proxy configurations.

Implementation

Enable SSR in Astro

Update astro.config.mjs to enable SSR:

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

export default defineConfig({
  output: 'server', // or 'hybrid'
  adapter: node({
    mode: 'standalone',
  }),
});

Install the Node adapter if not already installed:

npm install @astrojs/node

Configure Environment Variables

Create or update .env:

# .env
analytics-tube_HOST=https://app.analytics.tube
# For self-hosted: analytics-tube_HOST=https://analytics.yourcompany.com

Create Middleware

Create src/middleware.ts (or .js for JavaScript):

// src/middleware.ts
import type { MiddlewareHandler } from 'astro';

const analytics-tube_HOST = import.meta.env.analytics-tube_HOST || 'https://app.analytics.tube';

export const onRequest: MiddlewareHandler = async (context, next) => {
  const { request } = context;
  const url = new URL(request.url);

  // Check if this is an analytics request
  if (url.pathname.startsWith('/analytics/')) {
    return proxyToanalytics-tube(request, url.pathname);
  }

  // Continue with normal request handling
  return next();
};

async function proxyToanalytics-tube(request: Request, pathname: string): Promise<Response> {
  try {
    // Map paths: /analytics/* → /api/*
    let analytics-tubePath = pathname.replace('/analytics/', '/api/');

    const analytics-tubeUrl = `${analytics-tube_HOST}${analytics-tubePath}`;

    // Get client IP from headers
    const forwardedFor = request.headers.get('x-forwarded-for');
    const clientIp = forwardedFor ? forwardedFor.split(',')[0].trim() : '127.0.0.1';

    // Forward request
    const proxyHeaders = new Headers(request.headers);
    proxyHeaders.set('X-Real-IP', clientIp);
    proxyHeaders.set('X-Forwarded-For', clientIp);

    const response = await fetch(analytics-tubeUrl, {
      method: request.method,
      headers: proxyHeaders,
      body: request.method !== 'GET' ? await request.text() : undefined,
    });

    // Return proxied response
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: response.headers,
    });
  } catch (error) {
    console.error('analytics-tube proxy error:', error);
    return new Response('Analytics proxy error', { status: 500 });
  }
}

For JavaScript projects:

// src/middleware.js
const analytics-tube_HOST = import.meta.env.analytics-tube_HOST || 'https://app.analytics.tube';

export const onRequest = async (context, next) => {
  const { request } = context;
  const url = new URL(request.url);

  if (url.pathname.startsWith('/analytics/')) {
    return proxyToanalytics-tube(request, url.pathname);
  }

  return next();
};

async function proxyToanalytics-tube(request, pathname) {
  try {
    // Map paths: /analytics/* → /api/*
    let analytics-tubePath = pathname.replace('/analytics/', '/api/');

    const analytics-tubeUrl = `${analytics-tube_HOST}${analytics-tubePath}`;
    const forwardedFor = request.headers.get('x-forwarded-for');
    const clientIp = forwardedFor ? forwardedFor.split(',')[0].trim() : '127.0.0.1';

    const proxyHeaders = new Headers(request.headers);
    proxyHeaders.set('X-Real-IP', clientIp);
    proxyHeaders.set('X-Forwarded-For', clientIp);

    const response = await fetch(analytics-tubeUrl, {
      method: request.method,
      headers: proxyHeaders,
      body: request.method !== 'GET' ? await request.text() : undefined,
    });

    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: response.headers,
    });
  } catch (error) {
    console.error('analytics-tube proxy error:', error);
    return new Response('Analytics proxy error', { status: 500 });
  }
}

Update Your Layout

Add the tracking script to your layout:

---
// src/layouts/Layout.astro
const { title } = Astro.props;
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{title}</title>

    <!-- analytics-tube Analytics -->
    <script src="/analytics/script.js" async data-site-id="YOUR_SITE_ID"></script>
  </head>
  <body>
    <slot />
  </body>
</html>

Start Development Server

npm run dev

Verify the Setup

  1. Open your site in a browser with Developer Tools
  2. Check Network tab: Requests should go to /analytics/*
  3. Verify in analytics-tube dashboard: Data should appear

How It Works

Astro middleware intercepts requests before they're processed:

  1. Request to /analytics/script.js enters middleware
  2. Middleware proxies to https://app.analytics.tube/api/script.js
  3. Client IP headers are preserved
  4. Response is returned to browser

Advanced Configuration

Caching with Astro

Add caching for scripts:

// src/middleware.ts
const cache = new Map<string, { response: Response; timestamp: number }>();
const CACHE_TTL = 3600000; // 1 hour in milliseconds

async function proxyToanalytics-tube(request: Request, pathname: string): Promise<Response> {
  // Cache GET requests for scripts
  if (request.method === 'GET' && pathname.endsWith('.js')) {
    const cached = cache.get(pathname);

    if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
      return cached.response.clone();
    }
  }

  // ... proxy logic

  // Cache the response
  if (request.method === 'GET' && pathname.endsWith('.js')) {
    cache.set(pathname, {
      response: response.clone(),
      timestamp: Date.now(),
    });
  }

  return response;
}

Conditional Proxying

Only proxy in production:

export const onRequest: MiddlewareHandler = async (context, next) => {
  const { request } = context;
  const url = new URL(request.url);

  // Only proxy in production
  if (import.meta.env.PROD && url.pathname.startsWith('/analytics/')) {
    return proxyToanalytics-tube(request, url.pathname);
  }

  return next();
};

Error Logging

Add detailed error logging:

async function proxyToanalytics-tube(request: Request, pathname: string): Promise<Response> {
  try {
    // ... proxy logic
  } catch (error) {
    const errorDetails = {
      message: error instanceof Error ? error.message : 'Unknown error',
      pathname,
      method: request.method,
      timestamp: new Date().toISOString(),
    };

    console.error('analytics-tube proxy error:', errorDetails);

    // Return error response
    return new Response(JSON.stringify({ error: 'Analytics proxy error' }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' },
    });
  }
}

Troubleshooting

Middleware not running

Problem: Requests bypass middleware.

Solution:

  1. Ensure SSR or hybrid mode is enabled in astro.config.mjs
  2. Verify middleware file is in src/middleware.ts (not in subdirectory)
  3. Check adapter is installed: npm install @astrojs/node

Static build issues

Problem: Build fails or proxy doesn't work in production.

Solution: Middleware requires server-side rendering. Ensure:

// astro.config.mjs
export default defineConfig({
  output: 'server', // Required for middleware
  adapter: node({
    mode: 'standalone',
  }),
});

Environment variables not loading

Problem: analytics-tube_HOST is undefined.

Solution:

  1. Check .env file is in project root
  2. Use import.meta.env.analytics-tube_HOST (not process.env)
  3. For production, set environment variables in your hosting platform

Deployment

Vercel

Deploy with Vercel adapter:

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

export default defineConfig({
  output: 'server',
  adapter: vercel(),
});

Netlify

Deploy with Netlify adapter:

npm install @astrojs/netlify
// astro.config.mjs
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify/functions';

export default defineConfig({
  output: 'server',
  adapter: netlify(),
});

Node.js Server

For self-hosting:

npm run build
node ./dist/server/entry.mjs