From PHP Shared Hosting to the Serverless Edge: Rebuilding Last2Lines with Vercel v0 and AWS Aurora
Last2Lines is an AI-powered social campaigning platform that unites global voices by having the world write a single poem, two lines at a time. To scale my advocacy efforts, I joined the "Hackathon: H0: Hack the Zero Stack with Vercel v0 and AWS Databases" hackathon where I completely re-architected the platform into a production-ready powerhouse using Vercel and AWS.
The Backstory
Last2Lines began as a quiet idea I could not shake: what if a poem did not belong to one person, but to everyone?
The original version lived at last2lines.com — a monolithic PHP application running on shared hosting, backed by a MySQL database, deployed by FTP, and styled with hand-rolled HTML and CSS. It worked. Just barely. Every time I wanted to add a feature, I was untangling decade-old patterns. Every spike in traffic made me hold my breath. The database was a single point of failure. The deployment pipeline was me, a terminal, and a prayer.
The concept, though, was worth more than the infrastructure. Last2Lines invites people to contribute exactly two lines of verse — a couplet — to a collective, living poem built around a cause. An environmental campaign. A human rights movement. A story that belongs to no single voice. Contributors do not need accounts. They do not need to be poets. They just need something to say.
When I heard about the H0: Hack the Zero Stack with Vercel v0 and AWS Databases hackathon, I did not hesitate. This was the prompt I had been waiting for. I entered the hackathon specifically to use it as the forcing function to rebuild Last2Lines the right way — with modern tooling, a serverless architecture, and a database infrastructure that could actually scale with the ambition of the project.
The Transformation
Starting Point: Vercel v0
I began, unusually, not with code. I started with Vercel v0 — the AI-powered design and code generation platform. What struck me immediately was not just the code it produced, but the conversation it enabled. I could describe a feature — "an admin dashboard table for authors with search, filter by campaign and country, and pagination" — and receive a coherent, type-safe, accessible React component that followed the project's existing design system.
This is not magic. It is architecture-aware code generation. v0 understood the shadcn/ui component library, the Next.js 16 App Router patterns, TypeScript server actions, and the design tokens I had defined in globals.css. It did not produce throwaway scaffolding I had to rewrite — it produced starting points I could build on.
The rebuild moved faster than anything I had shipped before. Not because corners were cut, but because the foundational decisions — component library, routing strategy, rendering model, type safety — were made once and applied consistently everywhere, with v0 as the implementation partner.
The Database: AWS Aurora PostgreSQL
The most consequential infrastructure decision was the database. I chose AWS Aurora PostgreSQL, and it changed how I think about data persistence entirely.
On the old stack, the database was a liability — a single MySQL instance on shared hosting with no backups I actually trusted, no connection pooling, and no way to scale without migrating to a new hosting tier. Schema changes required manual ALTER TABLE statements run through phpMyAdmin, with no migration history.
Aurora PostgreSQL gave me:
- IAM-authenticated connections. There are no hardcoded database passwords in the codebase. Authentication is handled through the AWS IAM role bound to the Vercel deployment environment. The @aws-sdk/rds-signer generates short-lived auth tokens at connection time. This is the kind of security posture that would have taken significant infrastructure work to achieve on the old stack.
- Managed, auto-scaling storage. The database grows with the project. There is no ALERT: disk 90% full email waiting in my future.
- A proper migration workflow. I maintain versioned SQL scripts (001-setup-schema.sql through 005-add-contact-submissions.sql) that document every schema decision. The database schema today — seven tables, sixteen indexes including a partial index on approved contributions for optimised poem rendering, and full ON DELETE CASCADE foreign key chains — evolved through incremental, reviewable migrations.
- The partial index on contributions is worth dwelling on. The most common public read path is rendering an ordered, approved poem for a campaign page. Rather than scanning every contribution row, Aurora uses a targeted B-tree index filtered to WHERE status = 'approved', ordered by (campaign_id, sequence_number). That one index covers the primary read path of the entire application.
The Application Architecture
The rebuilt application runs on Next.js 16 App Router, deployed on Vercel's edge network. The rendering model is a deliberate mix:
- React Server Components handle all data fetching. Campaign pages, the public directory, the admin dashboard — these load fresh from the database on every request without a client-side fetch waterfall.
- Server Actions handle all mutations. Creating a campaign, approving a contribution, submitting a contact form — these are typed async functions that run on the server, validate input, execute parameterised SQL against Aurora, and revalidate the relevant Next.js cache paths.
- Client Components are reserved for interactivity: the contribution form with its live character counter and email verification flow, the admin tables with their optimistic status updates, the contact form with its four submission types.

Architecture Diagram For Last 2 Lines
There is no REST API layer. There is no separate backend service. The database is Aurora. The server is Vercel. The surface area is small, and the type safety runs from the SQL query result through to the rendered JSX.
Features Built During the Hackathon
The rebuild is not a port. It is a reimagining. Features that would have taken weeks on the old stack were built in days:
- Campaign management — each campaign has its own status lifecycle (draft → active → paused → completed → archived), configurable AI moderation levels, seed couplets to prime the poem, a partners field, email verification settings per campaign, and a featured flag. None of this existed in the original application.
- AI moderation — every submitted couplet is pre-screened before human review. The moderation level (lenient, standard, strict) is configurable per campaign. The old PHP application had none of this.
- Email verification and notifications — public contributors can verify their email address with a one-time code before their contribution enters the review queue. Admins receive a formatted Resend notification for every contact form submission, with a reply-to set to the submitter's address. The old application used PHP's mail() function, unreliably.
- Full admin dashboard — campaigns, contributions with approval workflow, authors (with search, filter, pagination, and delete-with-cascade), and a contact submissions inbox where each message can be marked read, archived, or deleted. The old application had a rudimentary CRUD interface with no concept of workflow states.
- Contact form — four submission types (new campaign request, feedback, concern, general enquiry), each stored in the database and dispatched to the admin inbox by email, with type-specific fields that appear dynamically as the user selects a category.
- Public campaign pages — a hero section, the living poem rendered in sequence, a contribution form with real-time character counting and OTP verification, a campaign partners section, social sharing, and a donation link if the campaign has one.
What I Learned
The zero-config promise is real, but it requires taste. Vercel v0 generates code quickly. The quality of what you get is proportional to the quality of what you bring: clear design tokens, consistent component patterns, explicit type definitions. I invested time early in establishing the design system and data model. That investment compounded throughout the build.
Aurora PostgreSQL as an application database is underrated. The managed operational burden — no patching, no storage management, no backup configuration — freed me to focus on the schema and query design rather than database administration. IAM authentication removed an entire class of credential management problems.
Server Actions deserve more credit. The pattern of colocating data fetching in RSCs and mutations in server actions, with no intermediate API layer, reduces the surface area for bugs substantially. Every mutation is type-checked from the input validation through the SQL query. There is nowhere for a type mismatch to hide.
Optimistic UI and server revalidation complement each other. The admin tables update immediately on interaction (optimistic), then the server revalidates the page data in the background. The user sees instant feedback. The data stays consistent. This pattern, made easy by useTransition and revalidatePath, would have been a significant engineering effort to implement cleanly on the old stack.
The Project Today
Last2Lines is live. The database has campaigns. The poems are growing. The admin dashboard works. The contact form is connected to a real inbox.
The architecture that once required a shared hosting plan, manual FTP deployments, and a MySQL instance I did not fully control now runs on infrastructure that scales automatically, authenticates securely through IAM, deploys in seconds from a git push, and costs nothing until it has traffic worth paying for.
The poem is collective. The infrastructure, finally, is too.
P.S. This project was re-build using Vercel v0 and submitted to the H0: Hack the Zero Stack with Vercel v0 and AWS Databases hackathon.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article