Caddy + AstroJS subdomain redirect

I have a project built in AstroJS & Payload CMS. In Astro I am serving up routes for the main domain e.g /en and routes which should belong on a subdomain eg /en/site/* The intention is that en/site is unavailable to the main domain and the subdomain always defaults to en/site/* Could I achieve this with Caddy? I was going to add this template to my project https://railway.app/template/wifz4K
5 Replies
Percy
Percy8mo ago
Project ID: 38aae1e6-ddf3-4d10-a02b-a590e97275f4
donutmuncha
donutmuncha8mo ago
38aae1e6-ddf3-4d10-a02b-a590e97275f4
Brody
Brody8mo ago
the template you link would provide a good way for you to deploy caddy with a Caddyfile to railway, but what you are talking about doing would be quite an involved process and you would need to have quite a good understanding of caddy and the Caddyfile syntax to achieve this
donutmuncha
donutmuncha8mo ago
I've come up with the following:
{
admin off
persist_config off
auto_https off
log {
format console
}
servers {
trusted_proxies static private_ranges
}
}

# Handle the API subdomain
api.{$RAILWAY_PUBLIC_DOMAIN} {
reverse_proxy {$RAILWAY_SERVICE_PAYLOAD_API_URL}
}

# Handle the Analytics subdomain
analytics.{$RAILWAY_PUBLIC_DOMAIN} {
reverse_proxy {$RAILWAY_SERVICE_PLAUSIBLE_URL}
}

# Handle the main website and www subdomain
{$RAILWAY_PUBLIC_DOMAIN}, www.{$RAILWAY_PUBLIC_DOMAIN} {
# Redirect / to /en/ by default
redir / /en/ permanent

# Reverse proxy to the AstroJS server for SSR
reverse_proxy / {$RAILWAY_SERVICE_ASTRO_URL}

# Redirect any /{locale}/site requests to suppliers.{$RAILWAY_PUBLIC_DOMAIN}
@localeSite path_regexp locale /([a-zA-Z]{2})/site*
redir @localeSite https://suppliers.{$RAILWAY_PUBLIC_DOMAIN}{uri}

# Health check endpoint
respond /health 200
}


# Handle the suppliers subdomain
suppliers.{$RAILWAY_PUBLIC_DOMAIN} {
# Redirect any /{locale} requests to the main website
@locale path_regexp locale /([a-zA-Z]{2})($|/*)
redir @locale https://{$PUBLIC_URL}{uri}

# Reverse proxy to the AstroJS server for SSR, specifically for suppliers
reverse_proxy / {$RAILWAY_SERVICE_ASTRO_URL}
}
{
admin off
persist_config off
auto_https off
log {
format console
}
servers {
trusted_proxies static private_ranges
}
}

# Handle the API subdomain
api.{$RAILWAY_PUBLIC_DOMAIN} {
reverse_proxy {$RAILWAY_SERVICE_PAYLOAD_API_URL}
}

# Handle the Analytics subdomain
analytics.{$RAILWAY_PUBLIC_DOMAIN} {
reverse_proxy {$RAILWAY_SERVICE_PLAUSIBLE_URL}
}

# Handle the main website and www subdomain
{$RAILWAY_PUBLIC_DOMAIN}, www.{$RAILWAY_PUBLIC_DOMAIN} {
# Redirect / to /en/ by default
redir / /en/ permanent

# Reverse proxy to the AstroJS server for SSR
reverse_proxy / {$RAILWAY_SERVICE_ASTRO_URL}

# Redirect any /{locale}/site requests to suppliers.{$RAILWAY_PUBLIC_DOMAIN}
@localeSite path_regexp locale /([a-zA-Z]{2})/site*
redir @localeSite https://suppliers.{$RAILWAY_PUBLIC_DOMAIN}{uri}

# Health check endpoint
respond /health 200
}


# Handle the suppliers subdomain
suppliers.{$RAILWAY_PUBLIC_DOMAIN} {
# Redirect any /{locale} requests to the main website
@locale path_regexp locale /([a-zA-Z]{2})($|/*)
redir @locale https://{$PUBLIC_URL}{uri}

# Reverse proxy to the AstroJS server for SSR, specifically for suppliers
reverse_proxy / {$RAILWAY_SERVICE_ASTRO_URL}
}
I might have mixed up the Railway variables. I will give this a go in a new railway environmen I think Astro middleware would even suffice.
// src/middleware.ts

import { defineMiddleware } from 'astro:middleware';

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

// Extract the locale from the path, default to 'en' if not present
const localeMatch = pathname.match(/^\/([a-z]{2})(\/|$)/i);
const locale = localeMatch ? localeMatch[1] : 'en';

// Check if we are in development environment
const isDevelopment = hostname === 'localhost';

// Redirect root to default locale 'en'
if (pathname === '/') {
const destination = isDevelopment ? `http://${hostname}:3000/${locale}/` : `${url.origin}/${locale}/`;
return Response.redirect(destination, 301);
}

// Handle requests to the /site path on suppliers subdomain or localhost in development
if ((hostname.startsWith('suppliers.') || isDevelopment) && pathname.startsWith(`/${locale}/site`)) {
// Continue with the request, Astro will render the appropriate page
return next();
}

// Redirect /site requests from main domain to suppliers subdomain
if (!hostname.startsWith('suppliers.') && !isDevelopment && pathname.startsWith(`/${locale}/site`)) {
const destination = `https://suppliers.${url.host}/${locale}/site`;
return Response.redirect(destination, 301);
}

// Redirect any /{locale} requests from suppliers subdomain to main domain
if (hostname.startsWith('suppliers.') && !isDevelopment && pathname.startsWith(`/${locale}/`) && !pathname.startsWith(`/${locale}/site`)) {
const destination = `https://${url.host.replace('suppliers.', '')}/${locale}/`;
return Response.redirect(destination, 301);
}

// Continue with the request for all other paths
return next();
});
// src/middleware.ts

import { defineMiddleware } from 'astro:middleware';

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

// Extract the locale from the path, default to 'en' if not present
const localeMatch = pathname.match(/^\/([a-z]{2})(\/|$)/i);
const locale = localeMatch ? localeMatch[1] : 'en';

// Check if we are in development environment
const isDevelopment = hostname === 'localhost';

// Redirect root to default locale 'en'
if (pathname === '/') {
const destination = isDevelopment ? `http://${hostname}:3000/${locale}/` : `${url.origin}/${locale}/`;
return Response.redirect(destination, 301);
}

// Handle requests to the /site path on suppliers subdomain or localhost in development
if ((hostname.startsWith('suppliers.') || isDevelopment) && pathname.startsWith(`/${locale}/site`)) {
// Continue with the request, Astro will render the appropriate page
return next();
}

// Redirect /site requests from main domain to suppliers subdomain
if (!hostname.startsWith('suppliers.') && !isDevelopment && pathname.startsWith(`/${locale}/site`)) {
const destination = `https://suppliers.${url.host}/${locale}/site`;
return Response.redirect(destination, 301);
}

// Redirect any /{locale} requests from suppliers subdomain to main domain
if (hostname.startsWith('suppliers.') && !isDevelopment && pathname.startsWith(`/${locale}/`) && !pathname.startsWith(`/${locale}/site`)) {
const destination = `https://${url.host.replace('suppliers.', '')}/${locale}/`;
return Response.redirect(destination, 301);
}

// Continue with the request for all other paths
return next();
});
I'm going to mark this as solved and move the discussion to Astro discord.
Brody
Brody8mo ago
sounds good!