I built a platform to translate PDFs into your chosen language and share them, with Vercel × AWS
A write-up of "Hiravi," a service that turns PDF slide decks into web-native slideshows and translates them into a language you pick from the 75+ supported by Amazon Translate — built on S3, SQS, Lambda, and Aurora DSQL. I share the design decisions and pitfalls from the hackathon.
This blog post was written for H0: Hack the Zero Stack with Vercel v0 and AWS Databases .
It's a hackathon themed around building something by combining Vercel v0 and AWS Databases, and I submitted "Hiravi," a slide translation and sharing platform.
My Project: https://devpost.com/software/hiravi
Why I built it — a lesson from JAWS-UG
I'm active in JAWS-UG, the AWS user community.
I serve on the organizing team of my local JAWS-UG Saga chapter, and in 2024 I was involved in JAWS PANKRATION 2024 , an international online event made possible with the help of AWS Heroes and Community Builders from around the world.
JAWS-UG is built on four guiding principles :
- Have fun! — As users of a cloud that everyone can access equally, we connect regardless of individual differences and delight in sharing growth and discovery.
- Make a difference! — We contribute creatively and positively to the better societal change that new technology brings.
- Go global! — AWS user groups exist all over the world, with lively exchange across countries and regions. We take part in that flat, worldwide exchange and contribute to the growth of the international user community.
- Find new heroes! — We believe one attempt at giving a talk leads to more growth than attending a hundred study sessions, and we share the opportunity for anyone who contributes to JAWS-UG to become a hero.
Go global! in particular hit home.
The more I got involved in cross-border events like JAWS PANKRATION, the more strongly I felt one problem: it's surprisingly hard to share your presentation slides across the language barrier. Japanese slides don't reach overseas attendees easily, and English slides don't reach the Japanese community easily.
And when you try to translate them, running a PDF through a translation service tends to break the layout or strip away the original design.
"I want materials to be readable in the reader's own language, while keeping their look exactly as is." Hiravi was born from this firsthand experience.
What Hiravi does
The approach I took is this: never touch the original slide images — just overlay the translated text as a separate layer on top. Hiravi is the slide-sharing platform I built around that idea, and it's my submission to Track 3, "Million-scale Global App," of H0 Hackathon 2026.
The name Hiravi is a coined word that combines hirari (the light, fluttering motion of paper turning over), visual (the visual nature of slides), and hiraku (to open). I wanted the name to capture the image of the product: flipping through slides and opening them up to the world.
What it does is simple:
- You upload a PDF slide deck.
- Each page is converted into an image for web display.
- The text layer is extracted and translated into a language you pick from the 75+ supported by Amazon Translate.
- You can share it at a short URL like
/@username/abcdefghij.
The target language is chosen by the user at upload time (the 75+ languages supported by Amazon Translate are the candidates). This article focuses on the architecture and design decisions behind building the frontend on Vercel (Next.js 16) and the backend on AWS (S3 / SQS / Lambda / Aurora DSQL / Amazon Translate), rather than diving into the source code itself.
For implementation details, please see the GitHub repository.
- Repository: github.com/midnight480/h0-hackathon

Overall Architecture
The data flow splits into two parts: the upload path and the viewing path.
The key point is that the heavy work (PDF → image conversion, translation) runs asynchronously, decoupled from the user's request.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Browser
│ 1. Sign in (Clerk)
│ 2. Open /upload
▼
Vercel (Next.js 16 / Server Actions)
│ 3. Request a presigned POST
▼
Amazon S3 ── 4. Issue presigned URL
▲
│ 5. Browser PUTs the PDF directly
Browser
│
Vercel ── 6. INSERT deck (status=pending) → Aurora DSQL
── 7. Enqueue job → Amazon SQS
│ 8. Trigger
▼
AWS Lambda (Python 3.12)
9. Fetch PDF from S3
10. Render to WebP with PyMuPDF
11. Extract text → Amazon Translate
12. UPDATE Aurora DSQL to status=ready
13. Call Vercel's revalidate webhookHere is the tech stack by role:
- Frontend: Next.js 16 (App Router), React 19, Tailwind CSS v4, shadcn/ui. All AWS calls go through Server Actions.
- Hosting: Vercel (ISR + Web Analytics + Speed Insights)
- Database: Aurora DSQL (serverless, distributed PostgreSQL). Connected via an IAM auth token.
- Storage: Amazon S3 (versioned, encrypted, CORS-scoped). Direct browser upload via presigned POST.
- Queue: Amazon SQS + Dead Letter Queue
- Compute: AWS Lambda (Python 3.12 / 2 GB / 15 min). PyMuPDF for rendering and text extraction.
- Translation: Amazon Translate (75+ languages)
- Auth: Clerk
- IaC: AWS CDK (TypeScript, 6 stacks)
Below, I'll highlight six design decisions — mostly around AWS — that I'm glad I made.
Design Decision 1: Upload directly from the browser to S3
Passing a PDF of tens of MB through a Vercel server function would eat into the function's execution time and payload limits.
So I used a presigned POST to upload directly from the browser to S3.
The only thing Vercel touches is issuing the signed URL for a brief moment — the PDF itself never goes through a server function.
What I liked is that I could bake the upload size limit into the S3 signing conditions (
content-length-range). Rather than rejecting it with a separate rate limiter, even if a client tampers with the value, S3 itself refuses it with
EntityTooLarge. Having the size limit enforced server-side and made tamper-proof was a big reassurance.
Design Decision 2: Decouple processing with S3 → SQS → Lambda
Converting a PDF into images, extracting text per page, and translating it into the selected languages takes anywhere from a few seconds to tens of seconds.
Doing that synchronously inside the upload request would keep the user waiting the whole time.
So after the upload completes, I just enqueue a job onto SQS and return a response immediately. SQS triggers a Lambda, and the heavy work proceeds in the background.
Meanwhile the user's screen shows "processing (pending)," and once it's done the display switches over via the webhook described later. Failed messages are parked in a Dead Letter Queue, so nothing slips through unnoticed.
A subtle but important detail: the file key passed to SQS is re-fetched from the value stored in the DB, not the value received from the client. This prevents an attack where someone specifies another user's file key and registers it as their own deck.
Design Decision 3: Convert PDF to WebP and extract text in Lambda
In the Lambda (Python 3.12), I use PyMuPDF to render each PDF page into a high-resolution WebP image and store it in S3.
For text extraction, instead of just grabbing all the text, my particular care went into excluding "noise that's meaningless to translate," such as page numbers and footers/headers. After normalizing each text block's coordinates as a ratio of the page's width and height, I apply these conservative rules:
- Drop blocks that are just numbers (page numbers).
- Drop running footers/headers — identical text that repeatedly appears in the top/bottom zones — if it shows up on 50% or more of all pages.
The bias here is to tolerate "a little noise left behind" rather than "accidentally erasing body text." This keeps meaningless translations like "3" or "Confidential" from creeping into the results.
Design Decision 4: Call Amazon Translate in parallel
Since the translation workload is "number of slides × number of target languages," processing it serially would be slow.
So I run it with up to 50 parallel threads to compress the wait time.
I let the source language be auto-detected with
auto, so there's no need to make the user declare "this is Japanese" at upload time. On the other hand, Amazon Translate has a throughput limit (requests per second), so I cap the thread count to prevent runaway requests. I also built in fault tolerance: if an individual block's translation fails due to a transient error, it falls back to the original text rather than marking the whole slide as failed.
Design Decision 5: Use Aurora DSQL with IAM auth + OCC retries
The most challenging part this time was Aurora DSQL.
It's a serverless, distributed PostgreSQL, and it has two distinctive characteristics.
First: no static password.
On each connection, it issues a short-lived IAM auth token to use in place of a password.
Since you don't store a DB password anywhere, secret management gets much easier.
Second: optimistic concurrency control (OCC).
Because DSQL uses optimistic control rather than locking, a write conflict returns
40001 (serialization_failure). The right answer wasn't to "avoid" this, but to assume it happens and retry with exponential backoff.
On the frontend side too, when a UNIQUE collision or OCC conflict occurs while assigning a deck ID (
slug), I retry the same way. The important mental shift was: "in a distributed DB, conflicts aren't an exceptional case — they're normal operation."
Another thing that tripped me up: DSQL doesn't support array types (
TEXT[]). Array-like fields such as tags and target_languages have to be held as JSONB, and indexes need to be created asynchronously (CREATE INDEX ASYNC).Design Decision 6: Overlay translations without breaking the original image
The core concept of Hiravi is never altering the original slide.
Translation results are stored separately from the original image and layered on top of it at display time.
To do this, the Lambda generates layout information per text block: its position (coordinates held as ratios), font size, background color, and the original text plus the translation for each language. For the background color, I took care to sample only the outer ring of the block and take the median, so as not to pick up the color of the text itself.
This lets the viewer toggle between "original slide view," "text panel view," and "translation overlay view." Because the coordinates are held as ratios, the layout doesn't break when the screen size changes.


Pitfalls and Lessons
- Design for distributed-DB conflicts as expected behavior. Aurora DSQL's
40001is unavoidable, so write retries in from the start. Bolting them on later means going back and adding them to every INSERT site. - Design the schema assuming no array types. Pushing
TEXT[]into JSONB and creating indexes asynchronously — knowing these DSQL conventions up front reduces rework. - Offload heavy work to a queue early. "Just run it synchronously for now and make it async later" ends up being a redo once you factor in state management (pending → ready → failed). Going SQS-first from the start was the right call.
- Preprocessing for translation (noise removal) determines quality. Just dropping page numbers and footers dramatically changed how the translated output looked.
- Enforce security server-side. Upload size via the S3 signing conditions, file-key ownership via re-fetching from the DB — each designed so the client is never trusted.
Aside: My history with v0 by Vercel
In line with the H0 Hackathon theme of "build with Vercel v0 and AWS Databases," I assembled Hiravi's frontend UI with v0 by Vercel.
Actually, this wasn't my first time with v0 — I've used it on and off for the past two to three years. For example, I've applied it to sites like SAGA IT Community Day 2025 Winter .
v0 first launched as
v0.dev with a beta in 2023, and its general availability was announced in August 2024, opening it up to the public (it's now v0.app). I'd only touched it lightly around release, but using it in earnest for this hackathon, I felt the experience of "spinning up a UI draft from a prompt all at once" has become far more practical than it was back then.
Being able to hand off the frontend's initial velocity to v0 — so I could spend my brainpower on the AWS-side architecture — made a big difference.
Conclusion
Hiravi takes the idea of "just toss in a PDF and it becomes a slide deck the whole world can read" and brings it to life by combining managed services from Vercel and AWS.
- Decouple uploads and heavy work with S3 → SQS → Lambda
- Translate in parallel into the chosen language (picked from 75+) with Amazon Translate
- Operate Aurora DSQL with IAM auth + OCC retries
- Layer translations as an overlay without breaking the original image
Aurora DSQL in particular — with its ease of being password-free and serverless — was also a great opportunity to learn the "distributed-DB" conventions like OCC and the lack of array types. I'd be glad if this helps anyone building global-facing apps in the same way.
The source code and detailed architecture diagrams are all in the GitHub repository.
- Repository: github.com/midnight480/h0-hackathon
#H0Hackathon
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article