├── .gitignore ├── README.md ├── bun.lockb ├── package.json ├── src └── index.tsx ├── sst-env.d.ts ├── sst.config.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | 177 | # sst 178 | .sst 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SST Puppeteer PDF Generator 2 | 3 | This project is a serverless PDF generator built with SST, Puppeteer, and Hono. It creates PDFs from HTML/JSX templates and stores them in an S3 bucket, providing a signed URL for downloading the generated PDF. 4 | 5 | ## Features 6 | 7 | - Serverless architecture using SST 8 | - PDF generation with Puppeteer 9 | - API built with Hono 10 | - S3 storage for generated PDFs 11 | - Customizable HTML/JSX templates 12 | 13 | ## Prerequisites 14 | 15 | - [Node.js](https://nodejs.org/) 16 | - [Bun](https://bun.sh/) 17 | - AWS account and configured credentials 18 | 19 | ## Installation 20 | 21 | To install dependencies: 22 | 23 | ```bash 24 | bun install 25 | ``` 26 | 27 | ## Running the Project 28 | 29 | To run: 30 | 31 | ```bash 32 | bun sst dev 33 | ``` 34 | 35 | This command starts the SST development environment, allowing you to test and debug your application locally. 36 | 37 | ## Project Structure 38 | 39 | - `src/index.tsx`: Hono API handler 40 | - `sst.config.ts`: SST configuration file 41 | 42 | ## How It Works 43 | 44 | 1. The application exposes a GET endpoint at the root ("/"). 45 | 2. When a request is made, it accepts an optional "name" query parameter. 46 | 3. An HTML template is generated using the provided name (or "world" if no name is given). 47 | 4. Puppeteer is used to create a PDF from the HTML/JSX template. 48 | 5. The generated PDF is uploaded to an S3 bucket. 49 | 6. A signed URL for the PDF is generated and returned in the response. 50 | 51 | ## API Usage 52 | 53 | Make a GET request to the root endpoint: 54 | 55 | ``` 56 | GET /?name=John 57 | ``` 58 | 59 | Response: 60 | 61 | ```json 62 | { 63 | "success": true, 64 | "url": "https://your-signed-s3-url.com/path/to/pdf" 65 | } 66 | ``` 67 | 68 | ## Customization 69 | 70 | You can customize the HTML/JSX template by modifying the Template component in `src/index.tsx`. 71 | 72 | ## Deployment 73 | 74 | To deploy the application to your AWS account: 75 | 76 | ```bash 77 | bun sst deploy --stage production 78 | ``` 79 | 80 | ## Local Development 81 | 82 | For local development, the application uses a local Chromium executable. Make sure the path in LOCAL_EXECUTABLE_PATH is correct for your system. See [this](https://sst.dev/docs/examples/#puppeteer-in-lambda) guide for more information. 83 | 84 | ## Credits 85 | 86 | - [SST Puppeteer Example](https://sst.dev/docs/examples/#puppeteer-in-lambda) 87 | - [Pontus Abrahamsson Cloudflare Example](https://x.com/pontusab/status/1832772994219008180) 88 | 89 | --- 90 | 91 | This project was created using `bun init` in bun v1.1.27. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. 92 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msmps/sst-puppeteer-pdf-generator/9160fa423060777f4ef99a99f77b96d1ebcf1fdc/bun.lockb -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sst-puppeteer-pdf-generator", 3 | "module": "src/index.ts", 4 | "type": "module", 5 | "devDependencies": { 6 | "@types/aws-lambda": "8.10.145", 7 | "@types/bun": "latest" 8 | }, 9 | "peerDependencies": { 10 | "typescript": "^5.0.0" 11 | }, 12 | "dependencies": { 13 | "@aws-sdk/client-s3": "^3.645.0", 14 | "@aws-sdk/s3-request-presigner": "^3.645.0", 15 | "@sparticuz/chromium": "127.0.0", 16 | "hono": "^4.5.11", 17 | "puppeteer-core": "23.1.1", 18 | "sst": "3.0.132" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | GetObjectCommand, 3 | PutObjectCommand, 4 | S3Client, 5 | } from "@aws-sdk/client-s3"; 6 | import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; 7 | import chromium from "@sparticuz/chromium"; 8 | import { Hono } from "hono"; 9 | import { handle } from "hono/aws-lambda"; 10 | import type { FC } from "hono/jsx"; 11 | import puppeteer from "puppeteer-core"; 12 | import { Resource } from "sst"; 13 | 14 | const LOCAL_EXECUTABLE_PATH = 15 | "/tmp/localChromium/chromium/mac_arm-1353446/chrome-mac/Chromium.app/Contents/MacOS/Chromium"; 16 | 17 | const app = new Hono(); 18 | const s3 = new S3Client({}); 19 | 20 | const Template: FC<{ name: string }> = ({ name }) => { 21 | return ( 22 | 23 | 24 |

Hello {name || "world"}!

25 | 26 | 27 | ); 28 | }; 29 | 30 | app.get("/", async (c) => { 31 | try { 32 | const name = await c.req.query("name"); 33 | const fileName = `pdf-${crypto.randomUUID()}.pdf`; 34 | const html = await c.html(