├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── src ├── index.ts └── server.ts └── tsConfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "semi": true, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "es5", 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `astro-lambda-adapter` 2 | 3 | The primary objective here is to map a `APIGatewayProxyEventV2` received in a Lambda to an Astro/Node.js `Request` and then Astro's `Response` to `APIGatewayProxyResult`. 4 | 5 | See [./src/server.ts](./src/server.ts). 6 | 7 | ## Astro SSR AWS Lambda Adapter 8 | 9 | > ⚠️ Still really early days on this one. Request `body` still unsupported. No deploys with complex Astro apps. Generally untested. Feedback and PRs welcome. 10 | 11 | ```sh 12 | npm i astro-lambda-adapter 13 | ``` 14 | 15 | ```js 16 | // ./astro.config.js 17 | import { defineConfig } from 'astro/config'; 18 | import awsAdapter from 'astro-lambda-adapter'; 19 | 20 | export default defineConfig({ 21 | adapter: awsAdapter(), 22 | outDir: './myLambda/dist', 23 | }); 24 | ``` 25 | 26 | ```js 27 | // ./myLambda/index.mjs 28 | import { handler as ssrHandler } from './dist/server/entry.mjs'; 29 | 30 | export async function handler(event) { 31 | console.log(`🚀 ${event.requestContext.http.method}: ${event.rawPath}`); 32 | return await ssrHandler(event); 33 | } 34 | ``` 35 | 36 | ```astro 37 | --- 38 | // ./src/pages/index.astro 39 | const RANDOM = Math.floor(Math.random() * 100) + 1; 40 | --- 41 | 42 | 43 | Astro on Lambda 44 | 45 | 46 |

Astro on Lambda

47 |

Random: {RANDOM}

48 | 49 | 50 | ``` 51 | 52 | > 📜 This integration doesn't modify how Astro uses Vite to build and bundle your app. You may find it beneficial to [configure Vite via Astro](https://docs.astro.build/en/reference/configuration-reference/#vite) to optimize the build for Lambda. 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-lambda-adapter", 3 | "description": "Astro AWS Lambda Adapter", 4 | "version": "0.1.2", 5 | "author": "tbeseda", 6 | "license": "MIT", 7 | "homepage": "https://github.com/tbeseda/astro-lambda-adapter", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/tbeseda/astro-lambda-adapter.git" 11 | }, 12 | "bugs": "https://github.com/tbeseda/astro-lambda-adapter/issues", 13 | "type": "module", 14 | "scripts": { 15 | "build": "npx esbuild src/index.ts src/server.ts --outdir=dist", 16 | "dev": "npm run build -- --watch" 17 | }, 18 | "files": [ 19 | "dist/**/*" 20 | ], 21 | "main": "./dist/index.js", 22 | "exports": { 23 | ".": "./dist/index.js", 24 | "./server": "./dist/server.js" 25 | }, 26 | "dependencies": { 27 | "@astrojs/webapi": "^0.11.1", 28 | "@types/node": "^17.0.24", 29 | "astro": "^1.0.0-beta.12" 30 | }, 31 | "devDependencies": { 32 | "@types/aws-lambda": "^8.10.93", 33 | "esbuild": "^0.14.36", 34 | "release-it": "^14.14.2" 35 | }, 36 | "keywords": [ 37 | "astro", 38 | "ssr", 39 | "aws", 40 | "lambda" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { AstroAdapter, AstroIntegration } from 'astro'; 2 | 3 | const NAME = 'astro-lambda-adapter'; 4 | 5 | function getAdapter(): AstroAdapter { 6 | return { 7 | name: NAME, 8 | serverEntrypoint: `${NAME}/server`, 9 | exports: ['handler'], 10 | }; 11 | } 12 | 13 | export default function createIntegration(): AstroIntegration { 14 | return { 15 | name: NAME, 16 | hooks: { 17 | 'astro:config:done': ({ setAdapter }) => { 18 | setAdapter(getAdapter()); 19 | }, 20 | }, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import type { SSRManifest } from 'astro'; 2 | import type { APIGatewayProxyEventV2, APIGatewayProxyResult } from 'aws-lambda'; 3 | import { NodeApp } from 'astro/app/node'; 4 | import { polyfill } from '@astrojs/webapi'; 5 | 6 | polyfill(globalThis, { 7 | exclude: 'window document', 8 | }); 9 | 10 | interface Args { 11 | host?: string; 12 | } 13 | 14 | export function createExports(manifest: SSRManifest) { 15 | const app = new NodeApp(manifest); 16 | 17 | return { 18 | async handler(event: APIGatewayProxyEventV2, args: Args = {}): Promise { 19 | const { body, cookies, headers: h, rawPath, rawQueryString, requestContext } = event; 20 | 21 | let hostString = ''; 22 | if (args.host) { 23 | hostString = args.host; 24 | } else if (h.host) { 25 | hostString = `${h['x-forwarded-protocol'] || 'https'}://${h.host}`; 26 | } 27 | const host = new URL(hostString); 28 | const fullUrl = new URL( 29 | `${rawPath}${rawQueryString.length > 0 ? `?${rawQueryString}` : ''}`, 30 | host 31 | ); 32 | 33 | const headers = new Headers(h as any); 34 | if (cookies) headers.append('cookie', cookies.join('; ')); 35 | 36 | const request = new Request(fullUrl.toString(), { 37 | method: requestContext.http.method, 38 | headers, 39 | // TODO: correctly handle body 40 | body: body && Object.keys(body).length ? body : null, 41 | }); 42 | 43 | try { 44 | const rendered = await app.render(request); 45 | const body = await rendered.text(); 46 | return { 47 | statusCode: rendered.status, 48 | headers: Object.fromEntries(rendered.headers.entries()), 49 | body, 50 | }; 51 | } catch (error: unknown) { 52 | throw error; 53 | } 54 | }, 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /tsConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "module": "ES2020", 9 | "moduleResolution": "node", 10 | "outDir": "./dist", 11 | "skipLibCheck": true, 12 | "strict": true, 13 | "target": "ES2020" 14 | } 15 | } 16 | --------------------------------------------------------------------------------