├── .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 |
--------------------------------------------------------------------------------