Astro
AWS.Website.Astro deploys an Astro project
to AWS. It runs Astro’s programmatic build with a wrangler-free AWS
Lambda adapter: server-rendered pages stream from a Lambda Function
URL, prerendered pages and client assets are served from a private S3
bucket through CloudFront, routed at the edge. Your astro.config.*
loads natively — there is no adapter to install into it and no
CloudFormation to write.
Install
Section titled “Install”The build integration is not bundled with alchemy. Install
@alchemy.run/frontend-frameworks; the resource loads its /astro
and /astro/aws exports from your project at deploy time. It is only
used at build time, so a dev dependency is enough:
bun add -d @alchemy.run/frontend-frameworksnpm install -D @alchemy.run/frontend-frameworkspnpm add -D @alchemy.run/frontend-frameworksyarn add -D @alchemy.run/frontend-frameworksConfigure Astro
Section titled “Configure Astro”Your astro.config.* file loads natively — integrations, Vite
plugins, and any other non-serializable options work exactly as they
do outside Alchemy. Alchemy merges a programmatic config over it:
the AWS Lambda adapter is injected for you (don’t declare an
adapter in your config — that fails the build with an actionable
error), and options passed via the astro prop override the file’s —
see Astro configuration below for the
details.
Declare the Website
Section titled “Declare the Website”Declare the site as a module-level const (rather than inline in the Stack):
import * as AWS from "alchemy/AWS";
export const Website = AWS.Website.Astro("Website");Add it to the Stack
Section titled “Add it to the Stack”Yield the site from your Stack and return its URL:
import * as Alchemy from "alchemy";import * as Effect from "effect/Effect";
export default Alchemy.Stack( "MyAstroSite", { providers: AWS.providers(), state: AWS.state(), }, Effect.gen(function* () { const site = yield* Website; return { url: site.url }; }),);Pages are server-rendered by default. Pages that
export const prerender = true are uploaded to S3 and served from
the edge without invoking the server.
See examples/aws-website-astro for the checked-in example.
Add environment variables
Section titled “Add environment variables”The server function’s environment is configured under
server.environment — plain values, or outputs from other resources
in the Stack:
export const Website = AWS.Website.Astro("Website", { server: { environment: { GREETING: "Hello from Alchemy!", API_BASE: api.url, }, },});The values are set on the Lambda at deploy time and injected into the
dev server’s process environment under alchemy dev, so server code
reads the same values in both modes.
Read the environment in server code
Section titled “Read the environment in server code”The Astro server runs in a plain Node Lambda, so server-rendered
pages and endpoints read the environment from process.env:
---const greeting = process.env.GREETING ?? "hello";---
<h1>{greeting}</h1>Fully static sites
Section titled “Fully static sites”With astro: { output: "static" } every page is prerendered at
build time and the deploy is assets-only — no Lambda is created, and
the S3 + CloudFront asset layer answers every request:
export const Website = AWS.Website.Astro("Website", { astro: { output: "static" }, errorPage: "404.html",});errorPage serves the built 404.html for unmatched routes; spa
answers misses with the index page (200) instead.
Astro configuration
Section titled “Astro configuration”Your astro.config.* is the primary place to configure Astro —
integrations, Vite plugins, and every other option (serializable or
not) work as usual:
import { defineConfig } from "astro/config";import react from "@astrojs/react";import tailwindcss from "@tailwindcss/vite";
export default defineConfig({ site: "https://blog.example.com", integrations: [react()], vite: { plugins: [tailwindcss()], },});The astro prop on the resource exposes common serializable options
for deploy-specific overrides; Astro merges them over the config
file (scalars override, arrays like integrations and
vite.plugins concatenate after the file’s):
export const Website = AWS.Website.Astro("Website", { astro: { site: "https://preview.example.com", },});Two options are managed for you regardless of the config file:
adapter— Alchemy injects its wrangler-free AWS Lambda adapter. Declaring an adapter inastro.config.*fails the build with an actionable error.output— Alchemy defaults it to"server"(superseding a file-leveloutput), so pages render on demand in the Lambda. Opt into a fully prerendered site withastro: { output: "static" }on the resource.
Local dev
Section titled “Local dev”alchemy dev runs Astro’s own dev server (native HMR) instead of
deploying; site.url is the local address and no AWS resources are
created. Wrap the site in Alchemy.remote() to deploy the real
infrastructure even during dev.
Custom domain
Section titled “Custom domain”const site = yield* AWS.Website.Astro("Web", { domain: { name: "app.example.com", hostedZoneId: zone.hostedZoneId, },});