Download endpoints return presigned S3 URLs instead of streaming bytes through Lambda

dropthejase converts three download-style routes - `/docx`, `/display`, and `/download` - from streaming file bytes through the API Lambda to returning a short-lived presigned S3 URL. The frontend then fetches directly from S3. Keeps large binary payloads out of the Lambda response path entirely.

infrastructureintegration

The three affected routes (GET /:documentId/docx, GET /:documentId/display, GET /download) previously called downloadFile() to pull bytes from S3 into Lambda memory, then wrote them to the response buffer. Each now calls getSignedUrl() with a 900-second TTL and returns a small JSON payload - { url, filename } or { url, type, filename } depending on the endpoint.

The frontend changes follow the same pattern across all call sites. useFetchDocxBytes, useFetchSingleDoc, DocDownloadBlock, and the DocPanel download button all do a two-step fetch: first hit the authenticated API endpoint to get the presigned URL, then fetch the actual bytes directly from S3 using that URL (no auth header needed - the presigned URL carries its own credentials). For DocDownloadBlock and the DocPanel button, a hidden <a> element is created with href set to the presigned URL and .click() triggered programmatically. The URL.createObjectURL / URL.revokeObjectURL pattern for blobs is gone.

One unrelated fix ships alongside: storage.ts was reading process.env.S3_BUCKET_NAME but CDK injects the bucket name as DOCS_BUCKET_NAME. That mismatch would have caused every download to fail before this was caught.

The display endpoint also changes its response shape slightly. It now returns { url, type, filename } where type is "pdf" if a PDF rendition is available for a DOCX, or the raw file_type otherwise. The frontend uses type === "pdf" to decide whether to pipe the S3 response to PDF.js or fall back to the docx-preview viewer.

So what Worth a look if you're running a similar Lambda-backed file serving setup and want to cut response latency and Lambda memory pressure. The pattern is straightforward to extract - the route-level change is small, and the two-step fetch pattern on the client side is self-contained. Presigned URL TTL is hardcoded at 900 seconds; adjust if your use case involves longer-lived links.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

7 commits from dropthejase/louis, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
a3c9174a fix: correct S3 bucket env var name S3_BUCKET_NAME → DOCS_BUCKET_NAME Jason Lee 2026-05-08 ↗ GitHub
CDK injects DOCS_BUCKET_NAME but storage.ts was reading S3_BUCKET_NAME,
causing every S3 document operation to throw in production.
0e63858f feat(api): convert /docx endpoint to return presigned S3 URL Jason Lee 2026-05-12 ↗ GitHub
649d97f1 feat(api): convert /display endpoint to return presigned S3 URL Jason Lee 2026-05-12 ↗ GitHub
4de594aa feat(api): convert /download endpoint to return presigned S3 URL Jason Lee 2026-05-12 ↗ GitHub
5d93492b feat(frontend): fetch PDF/DOCX bytes directly from S3 via presigned URLs Jason Lee 2026-05-12 ↗ GitHub
0bf14c52 feat(frontend): DocDownloadBlock opens presigned S3 URL directly Jason Lee 2026-05-12 ↗ GitHub
3ab69023 fix(frontend): DocPanel download button uses presigned S3 URL Jason Lee 2026-05-12 ↗ GitHub

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-342.md from inside the repo you want the changes in.

⬇ Download capture-thread-342.md