Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Astro's SSR (Server-Side Rendering) application class provides the runtime for deploying Astro sites to server environments through adapters.
The main application class for server-side rendering in production.
/**
* Main SSR application class
* Created from build output for production deployment
*/
class App {
constructor(manifest: SSRManifest, streaming?: boolean);
}Renders an incoming request to a Response.
/**
* Renders a request to a Response
* @param request - Incoming HTTP request
* @param options - Rendering options
* @returns Promise resolving to HTTP Response
*/
render(request: Request, options?: RenderOptions): Promise<Response>;
interface RenderOptions {
/**
* Route data for the request (from match())
*/
routeData?: RouteData;
/**
* Locals object for context
*/
locals?: App.Locals;
/**
* Client IP address
*/
clientAddress?: string;
/**
* Add Set-Cookie header to response
* @default true
*/
addCookieHeader?: boolean;
}import { App } from 'astro/app';
import manifest from './manifest.mjs';
const app = new App(manifest);
export default {
async fetch(request) {
const response = await app.render(request, {
clientAddress: request.headers.get('CF-Connecting-IP'),
locals: {},
});
return response;
},
};Matches a request to a route.
/**
* Matches a request to a route
* @param request - Incoming HTTP request
* @returns RouteData if matched, undefined otherwise
*/
match(request: Request): RouteData | undefined;import { App } from 'astro/app';
import manifest from './manifest.mjs';
const app = new App(manifest);
export default {
async fetch(request) {
// Match the route
const routeData = app.match(request);
if (!routeData) {
return new Response('Not Found', { status: 404 });
}
// Render with matched route
return app.render(request, { routeData });
},
};Returns an adapter-specific logger instance.
/**
* Gets the adapter logger
* @returns Integration logger for adapter use
*/
getAdapterLogger(): AstroIntegrationLogger;import { App } from 'astro/app';
import manifest from './manifest.mjs';
const app = new App(manifest);
const logger = app.getAdapterLogger();
logger.info('Adapter initialized');
logger.warn('Custom warning from adapter');Returns the list of allowed domains for remote images and forwarded hosts.
/**
* Gets the allowed domains from manifest
* @returns Array of remote patterns for allowed domains
*/
getAllowedDomains(): Partial<RemotePattern>[];import { App } from 'astro/app';
import manifest from './manifest.mjs';
const app = new App(manifest);
const allowedDomains = app.getAllowedDomains();
console.log('Allowed domains:', allowedDomains);Removes the configured base path from a pathname.
/**
* Removes base path from pathname
* @param pathname - Full pathname including base
* @returns Pathname without base prefix
*/
removeBase(pathname: string): string;import { App } from 'astro/app';
import manifest from './manifest.mjs';
const app = new App(manifest);
// If base is '/docs', this removes it
const withoutBase = app.removeBase('/docs/guide/intro');
// Result: 'guide/intro'Extracts Set-Cookie headers from a Response object as an instance method.
/**
* Extracts Set-Cookie headers from Response (instance method)
* @param response - Web API Response
* @returns Array of cookie strings
*/
setCookieHeaders(response: Response): string[];import { App } from 'astro/app';
import manifest from './manifest.mjs';
const app = new App(manifest);
const response = await app.render(request);
const cookies = app.setCookieHeaders(response);
console.log('Cookies to set:', cookies);Extracts Set-Cookie headers from a Response object. This is a static method on the App class.
/**
* Extracts Set-Cookie headers from Response
* @param response - Web API Response
* @returns Array of cookie strings
*/
static getSetCookieFromResponse(response: Response): string[];import { App } from 'astro/app';
const response = await app.render(request);
const cookies = App.getSetCookieFromResponse(response);
console.log('Cookies to set:', cookies);Validates a forwarded host header against allowed host patterns. This is a static method on the App class.
/**
* Validates forwarded host header
* Checks if forwarded host matches allowed host patterns
* @param forwardedHost - Forwarded host value from X-Forwarded-Host header
* @param allowedHosts - Array of allowed host patterns (supports wildcards)
* @param protocol - Protocol (http or https)
* @returns Whether the host is valid
*/
static validateForwardedHost(
forwardedHost: string,
allowedDomains?: Partial<RemotePattern>[],
protocol?: string
): boolean;
interface RemotePattern {
protocol?: string;
hostname: string;
port?: string;
pathname?: string;
}import { App } from 'astro/app';
const forwardedHost = request.headers.get('x-forwarded-host');
if (forwardedHost && !App.validateForwardedHost(
forwardedHost,
['example.com', '*.example.com']
)) {
return new Response('Invalid host', { status: 403 });
}Deserializes a manifest from build output.
/**
* Deserializes a manifest from build output
* @param manifest - Serialized manifest
* @returns Deserialized SSR manifest
*/
function deserializeManifest(
manifest: SerializedSSRManifest
): SSRManifest;Node.js-specific exports for Node adapters.
// From astro/app/node
import { NodeApp } from 'astro/app/node';
import type { NodeAppOptions } from 'astro/app/node';The Node.js app provides additional functionality for Node.js-based adapters, including middleware integration and response handling.
interface SSRManifest {
/**
* Base URL for the site
*/
base: string;
/**
* Trailing slash behavior
*/
trailingSlash: 'always' | 'never' | 'ignore';
/**
* Compress HTML output
*/
compressHTML: boolean;
/**
* Assets configuration
*/
assets: Set<string>;
/**
* Assets prefix for CDN
*/
assetsPrefix?: string | Record<string, string>;
/**
* Entry modules
*/
entryModules: Record<string, string>;
/**
* Routes
*/
routes: RouteInfo[];
/**
* Renderers for framework components
*/
renderers: SSRLoadedRenderer[];
/**
* Client directives
*/
clientDirectives: Map<string, ClientDirective>;
/**
* Component metadata
*/
componentMetadata: Map<string, ComponentMetadata>;
/**
* Inlined scripts
*/
inlinedScripts: Map<string, string>;
/**
* i18n configuration
*/
i18n?: I18NConfig;
/**
* Middleware function
*/
middleware?: () => AstroMiddlewareInstance;
/**
* Check origin header
*/
checkOrigin: boolean;
}interface RouteData {
/**
* Route pattern (e.g., '/blog/[slug]')
*/
route: string;
/**
* Route type: 'page', 'endpoint', or 'redirect'
*/
type: 'page' | 'endpoint' | 'redirect';
/**
* URL pattern for matching
*/
pattern: RegExp;
/**
* Route parameter names
*/
params: string[];
/**
* Component path
*/
component: string;
/**
* Generate function for static paths
*/
generate: (data: any) => string;
/**
* Path segments
*/
segments: string[][];
/**
* Whether route is prerendered
*/
prerender: boolean;
/**
* Fallback routes for i18n
*/
fallbackRoutes: RouteData[];
/**
* Whether route is an index
*/
isIndex: boolean;
/**
* Route origin
*/
origin: 'internal' | 'external';
}interface RouteInfo {
/**
* Route data
*/
routeData: RouteData;
/**
* File path
*/
file: string;
/**
* Linked stylesheets
*/
links: string[];
/**
* Linked styles
*/
styles: StyleInfo[];
/**
* Linked scripts
*/
scripts: ScriptInfo[];
}Adapters use the App class to integrate Astro with deployment platforms.
interface AstroAdapter {
/**
* Adapter name
*/
name: string;
/**
* Server entrypoint
*/
serverEntrypoint?: string;
/**
* Previewable adapter
*/
previewEntrypoint?: string;
/**
* Exports for the entrypoint
*/
exports?: string[];
/**
* Additional arguments for entrypoint
*/
args?: any;
/**
* Adapter features
*/
supportedAstroFeatures?: AstroAdapterFeatures;
}interface AstroAdapterFeatures {
/**
* Static assets support
*/
staticAssets?: 'stable' | 'experimental' | 'unsupported';
/**
* Hybrid rendering support
*/
hybrid?: 'stable' | 'experimental' | 'unsupported';
/**
* Server output support
*/
serverOutput?: 'stable' | 'experimental' | 'unsupported';
/**
* Environment variables support
*/
envGetSecret?: 'stable' | 'experimental' | 'unsupported';
/**
* i18n domains support
*/
i18nDomains?: 'stable' | 'experimental' | 'unsupported';
}// Example adapter for a custom platform
import type { AstroAdapter, AstroIntegration } from 'astro';
export default function myAdapter(): AstroIntegration {
return {
name: 'my-adapter',
hooks: {
'astro:config:setup': ({ updateConfig }) => {
updateConfig({
vite: {
// Vite config
},
});
},
'astro:config:done': ({ setAdapter }) => {
setAdapter({
name: 'my-adapter',
serverEntrypoint: 'my-adapter/server.js',
supportedAstroFeatures: {
staticAssets: 'stable',
hybrid: 'stable',
serverOutput: 'stable',
},
});
},
'astro:build:done': async ({ dir }) => {
// Post-build processing
},
},
};
}// Server entrypoint for adapter
import { App } from 'astro/app';
import { polyfill } from '@astrojs/webapi';
polyfill(globalThis, {
exclude: 'window document',
});
export function createExports(manifest) {
const app = new App(manifest);
const handler = async (request) => {
// Get client IP from platform headers
const clientAddress =
request.headers.get('x-forwarded-for') ||
request.headers.get('x-real-ip');
// Render the request
const response = await app.render(request, {
clientAddress,
});
return response;
};
return { handler };
}import { App } from 'astro/app';
export default {
async fetch(request, env, ctx) {
const app = new App(manifest);
const response = await app.render(request, {
clientAddress: request.headers.get('CF-Connecting-IP'),
locals: { env, ctx },
});
return response;
},
};import { App } from 'astro/app';
export default async function handler(request) {
const app = new App(manifest);
return app.render(request, {
clientAddress: request.headers.get('x-forwarded-for'),
});
}import { App } from 'astro/app';
const app = new App(manifest);
export const handler = async (event, context) => {
const request = createRequestFromEvent(event);
const response = await app.render(request, {
clientAddress: event.headers['client-ip'],
});
return createResponseForNetlify(response);
};import { NodeApp } from 'astro/app/node';
import { createServer } from 'http';
const app = new NodeApp(manifest);
createServer(async (req, res) => {
const request = NodeApp.createRequest(req);
const response = await app.render(request);
await NodeApp.writeResponse(response, res);
}).listen(3000);Define types for locals in src/env.d.ts:
/// <reference types="astro/client" />
declare namespace App {
interface Locals {
// Platform-specific context
env?: {
DATABASE_URL: string;
API_KEY: string;
};
// User data
user?: {
id: string;
name: string;
};
// Request metadata
requestId?: string;
startTime?: number;
}
}The NodeApp class provides additional static methods for Node.js adapters.
class NodeApp extends App {
constructor(manifest: SSRManifest);
/**
* Creates a Web API Request from Node.js IncomingMessage
* @param req - Node.js request object
* @param options - Request creation options
* @returns Web API Request
*/
static createRequest(
req: import('http').IncomingMessage,
options?: {
skipBody?: boolean;
allowedDomains?: Array<Partial<RemotePattern>>;
}
): Request;
interface RemotePattern {
protocol?: string;
hostname: string;
port?: string;
pathname?: string;
}
/**
* Writes a Web API Response to Node.js ServerResponse
* @param response - Web API Response
* @param res - Node.js response object
* @returns Promise that resolves when writing completes
*/
static writeResponse(
response: Response,
res: import('http').ServerResponse
): Promise<void>;
/**
* Extracts Set-Cookie headers from Response
* @param response - Web API Response
* @returns Array of cookie strings
*/
static getSetCookieFromResponse(response: Response): string[];
/**
* Validates forwarded host header
* Checks if forwarded host matches allowed hosts
* @param forwardedHost - Forwarded host value
* @param allowedHosts - Array of allowed host patterns
* @returns Whether the host is valid
*/
static validateForwardedHost(
forwardedHost: string,
allowedHosts: string[]
): boolean;
/**
* Sets custom headers map for pathname-based header injection
* @param headers - Headers configuration by pathname
*/
setHeadersMap(headers: NodeAppHeadersJson): void;
}
type NodeAppHeadersJson = Array<{
pathname: string;
headers: Array<{
key: string;
value: string;
}>;
}>;Usage Examples:
import { NodeApp } from 'astro/app/node';
import { createServer } from 'http';
import { loadManifest } from 'astro/app/node';
const manifest = await loadManifest('./dist/server/manifest.json');
const app = new NodeApp(manifest);
const server = createServer(async (req, res) => {
// Validate forwarded host
const forwardedHost = req.headers['x-forwarded-host'];
if (forwardedHost && !NodeApp.validateForwardedHost(forwardedHost, ['example.com', '*.example.com'])) {
res.statusCode = 403;
res.end('Invalid host');
return;
}
// Create request with custom headers
const request = NodeApp.createRequest(req, {
headers: {
'x-custom-header': 'value',
},
});
// Render the page
const response = await app.render(request);
// Extract cookies before writing response
const cookies = NodeApp.getSetCookieFromResponse(response);
console.log('Setting cookies:', cookies);
// Write response
await NodeApp.writeResponse(response, res);
});
server.listen(3000);Utilities for loading manifests in Node.js environments.
/**
* Loads SSR manifest from filesystem
* @param manifestPath - Path to manifest.json file
* @returns Promise resolving to deserialized manifest
*/
async function loadManifest(rootFolder: URL): Promise<SSRManifest>;
/**
* Loads NodeApp with manifest from filesystem
* Convenience function combining loadManifest and NodeApp constructor
* @param rootFolder - URL to the root folder containing the manifest
* @returns Promise resolving to NodeApp instance
*/
async function loadApp(rootFolder: URL): Promise<NodeApp>;
/**
* Applies Node.js polyfills for Web APIs
* Required for some server environments
* Polyfills: fetch, Headers, Request, Response, crypto
*/
function applyPolyfills(): void;import { loadApp, applyPolyfills } from 'astro/app/node';
import { createServer } from 'http';
// Apply polyfills if needed
applyPolyfills();
// Load app from manifest
const app = await loadApp('./dist/server/manifest.json');
createServer(async (req, res) => {
const request = app.constructor.createRequest(req);
const response = await app.render(request);
await app.constructor.writeResponse(response, res);
}).listen(3000);// Base App class
import { App, deserializeManifest } from 'astro/app';
import type { RenderOptions, SSRManifest, SerializedSSRManifest } from 'astro/app';
// Node.js-specific exports
import { NodeApp, loadManifest, loadApp, applyPolyfills } from 'astro/app/node';
import type { NodeAppOptions } from 'astro/app/node';