Overview
Big news... I created my own personal wedding website π! Here it is below, in all its glory:

It comes equipped with the following pages:
- Home
- Wedding Weekend (weekend itinerary)
- TBD and Luc (the story of how my non-existent wife and I met)
- FAQs
- Travel (travel guidance)
- Accomodations
- RSVP
The RSVP Page π
The RSVP page, in particular, is where most of the magic happens β¨.

This page queries an authenticated database in real-time, using guest information provided by the user.
You can search for an RSVP for any of the following guests below:
| First Name | Last Name |
|---|---|
| John | Doe |
| Jane | Smith |
| Barack | Obama |
| Chuck | Norris |
| Luc | Marrie |
Project Goals
Initially when I started this project, I wanted to continue exploring Next.js (a React-based framework for building UIs) and to get my hands dirty with some kind of database... even if for a very lightweight use case.
To date, all websites I had built thus far:
- Were fully static in nature (page content is already set in stone)
- Or, when dynamic data is required, relied on simple
GETcalls to previously-existing APIs
As such, I wanted to pick a simple website use case that would require me to create my own database.
So... Why a Wedding Website?
You may have noticed the title of my website is "TBD and Luc". That's because I'm not actually getting married... (nor do I have a gf/fiancΓ©e lol).
I'm still a single man, traversing life's winding roads in solitude. However, I fret not because surely my unwavering pursuit of web dev knowledge will lead me to true love.

Anyway, prior to the π fall of 2023, I had yet to attend my first wedding. When it rains, it pours I guess... because suddenly three back-to-back weddings popped up on my calendar π¨.
In terms of the wedding sites, they were created by the following service providers:
Given that I literally help companies make websites for a living, it was interesting to examines websites for a totally different use case from my line of work... and to inspect the visual layout and feature differences between each provider.
In terms of similarities, features included:
- Several pages of static content (FAQs, photos, "how we met", etc.)
- Usually a registry
- In all cases, an RSVP page (where a user provides some sort of unique identifier, granting them read/write access to their RSVP info)
In any case, based on the described functionality, I decided a wedding website was a perfect use case for my project.

Website Development
Alright, now for the meat and potatoes π₯© π₯... how was this thing made?
Features
For an MVP, I decided to prioritize the following features:
| Feature | Description | MVP |
|---|---|---|
| Static Pages | Pages with static content (e.g. home, wedding schedule, FAQs, etc.). | β |
| RSVP (Read) | Let's user check on the status of their RSVP using first/last name. | β |
| RSVP (Write) | Let's user submit/update their RSVP information. | β |
| Registry | Registry page for that new Espresso machine I've had my eyes on. Doing this from scratch would require me to either build an e-commerce site essentially... so, I'm gonna pass for now. If I were to seriously pursue this, I'd probably use a URL 200 rewrite (or iframe) to a page hosted on Zola/The Knot. | β |
| Site Password Protection | Secure site with basic password protection. I will revisit this in the near future. | β |
Architecture
To support the above feature set, I loosely adhered to this tacky architecture diagram:

Website Ingredients
- Next.js (Front-end)
- Vercel (Hosting + serverless functions)
- Vercel Postgres (Backend database for all my lovely guests and their RSVPs)
- TailwindCSS (Styling)
Building the Static Pages
To kick things off, I scaffolded my project and created a blank canvas for each page using Next.js's quickstart flow, which now uses App Router by default (as opposed to Pages Router).
npx create-next-app@latest
Note that because I'm new to Next.js, everything described below is written exclusively with knowledge of app router only.
Pages
In Next.js, each folder is used to define a "route" (path at which a page is available in the browser). Creating a page file under each route is what makes the UI at that path publicly accessible. As such, I was able to create each page of my site using the directory structure below:
app βββ accomodations <-- defines URL β βββ page.tsx <-- UI here is exposed at folder name βββ faqs β βββ page.tsx βββ rsvp β βββ page.tsx βββ tbd-and-luc β βββ page.tsx βββ travel β βββ page.tsx βββ wedding-weekend β βββ page.tsx βββ globals.css βββ layout.tsx <-- automatically shared layout βββ page.tsx <-- home page
Shared Layout
In Next.js projects, layout is a special file. As Next.js defines it:
A layout is UI that is shared between multiple pages. On navigation, layouts preserve state, remain interactive, and do not re-render.
I was excited to see this feature. Each page in my app shares the same structure... so there's no need to re-render a fresh layout each time a page loads.
When a layout is defined at the app/ root, it's known as the Root Layout. This is basically a "global" layout; Next.js applies it to all pages in the app automatically.
I tossed a Navbar and Footer component into that bad boy straight away:
import Navbar from './components/navbar' import Footer from './components/footer' export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body className=""> <Navbar /> <-- π {children} <Footer /> <-- π </body> </html> ) }
Beyond that, the rest of my initial work was just plain old React development (components, styling, etc.).
The RSVP Page and Database
Next was the RSVP page. Here's how I decided it should work:
- A guest can enter their first/last name on the page
- If the guest exists in my database, return their RSVP information (i.e. the events they're currently RSVPed to)
- If the guest doesn't exist, display an error
Ladies and gentlemen... enter the database:

Picking a Database
For this segment of the project, I had two important questions to answer:
- What type of database should I use? π€ (e.g. relational, NoSQL, key-value store, graph... etc.)
- And from which database provider? π¨
I've played around with Supabase before, which was a great experience. Unfortunately they "pause" your DBs after a rather short period of inactivity... so I skipped out on them for now. (I will probably use them for my next project though).
Before I got much further in my search, I came across Vercel's Storage product; front-and-center on their website:
Vercel offers a suite of managed, serverless storage products that integrate with your frontend framework.
- Vercel KV: Durable Redis
- Vercel Postgres: Serverless SQL
- Vercel Blob: Large file storage
- Vercel Edge Config: Global, low-latency data store
Given I was already using Next.js and Vercel, it was compelling to see they also had a storage product. But wait, it gets better... the product page had an entire paragraph dedicated to CHOOSING a database type π±:
Choosing the correct storage solution depends on your needs for latency, durability, and consistency, among many other considerations.
To help you choose, we've created a table below to summarize the benefits of each storage option in relation to each other:
| Product | Reads | Writes | Use Case | Limits |
|---|---|---|---|---|
| KV | Fast | Milliseconds | Key/value and JSON data | Learn more |
| Postgres | Fast | Milliseconds | Structured, relational data | Learn more |
| Blob | Fast | Milliseconds | Large, content-addressable files ("blobs") | Learn more |
| Edge Config | Ultra-fast | Milliseconds | Runtime configuration (e.g., feature flags) | Learn more |
After a literal 5-second skim of the table, the obvious way to go was Postgres (thank you Vercel for your pristine documentation).
Creating the Database
First, I needed to create a database in the Vercel platform. To do so, I leveraged their (somewhat bare bones) UI for running queries:

Because it's 2023, I asked ChatGPT to construct me a relational table schema for guests, events, and RSVPs π₯΄. Based on its response, I created the following tables below:
CREATE TABLE guests ( id SERIAL PRIMARY KEY, first_name VARCHAR(255), last_name VARCHAR(255), email VARCHAR(255), num_of_guests INT, rsvp_message VARCHAR(255) ); CREATE TABLE events ( id SERIAL PRIMARY KEY, name VARCHAR(255), description TEXT, datetime TIMESTAMP WITH TIME ZONE, location VARCHAR(255) ); CREATE TABLE rsvps ( id SERIAL PRIMARY KEY, guest_id INT REFERENCES guests(id), event_id INT REFERENCES events(id), response BOOLEAN );
I inserted some guest, event, and rsvp records:
INSERT INTO guests (first_name, last_name, email, num_of_guests, rsvp_message) VALUES ('Luc', 'Marrie', 'luc@marrie.com', 2, 'I''m getting married?'), -- Guest 1 ('John', 'Doe', 'john.doe@example.com', 2, 'So excited!'), -- Guest 2 ('Jane', 'Smith', 'jane.smith@example.com', 2, 'Congrats!') -- Guest 3 ; INSERT INTO events (name, description, datetime, location) VALUES ('Welcome Dinner', null, '2030-09-20 17:00:00', null), -- Event 1 ('Wedding', null, '2030-09-21 16:30:00', null), -- Event 2 ('Farewell Brunch', null, '2030-09-22 10:00:00', null) -- Event 3 ; INSERT INTO rsvps (guest_id, event_id, response) VALUES (1, 1, true), -- Luc Marrie RSVPs yes to Welcome Dinner (1, 2, true), -- Luc Marrie RSVPs yes to Wedding (1, 3, true), -- Luc Marrie RSVPs yes to Farewell Brunch (2, 1, true), -- John Doe RSVPs yes to Welcome Dinner (2, 2, true), -- John Doe RSVPs yes to Wedding (2, 3, false), -- John Doe RSVPs no to Farewell Brunch (3, 1, false), -- Jane Smith RSVPs no to Welcome Dinner (3, 2, false), -- Jane Smith RSVPs yes to Wedding (3, 3, true) -- Jane Smith RSVPs yes to Farewell Brunch ;
VoilΓ - we were ready to go.

Local Development
Now that the tables were set up, I could hook up the database to the frontend.
I used Vercel's Postgres quickstart guide to get going. They weren't lying when they said "quick" start... considering how much of a noob I am, it was genuinely very easy for me to get started.
With a single command, I could fetch all necessary environment variables from Vercel and save them to a file called .env.development.local.:
vercel env pull .env.development.local
From there, I could instantly interact with my database using a combination of their SDK and serverless functions. Refer to the code block below, which shows a simple query against my guests table.
// app/api/find-invitation/route.ts import { sql } from '@vercel/postgres'; import { NextResponse } from 'next/server'; export async function GET(request: Request) { try { const result = await sql`SELECT * FROM guests;`; return NextResponse.json({ result }, { status: 200 }); } catch (error) { return NextResponse.json({ error }, { status: 500 }); } }
Feel free to examine my finished serverless function in GitHub. To summarize how it works:
-
When a guest clicks "Find Your Invitation", my app calls the serverless function with first/last name as query params:
const response = await fetch(`/api/find-invitation? firstName=${firstName}&lastName=${lastName}`); -
The function extracts the first/last name, and makes the following SQL query to find the guest
idSELECT * FROM guests WHERE first_name ILIKE ${firstName} AND last_name ILIKE ${lastName} ; -
When a guest is found, the function queries the
eventstable and joins it to thersvpstable based using the guestidSELECT events.id, events.name, events.description, events.datetime, events.location, rsvps.response FROM events LEFT JOIN rsvps ON events.id = rsvps.event_id AND rsvps.guest_id = ${guestId} ;
As such, the serverless function is able to return the following JSON back to my frontend:
{ "guest": { "id": 2, "first_name": "John", "last_name": "Doe", "number_of_guests": 2, "rsvp_message": "My wife and I are so excited to see you get married... feels like just yesterday I taught you how to write serverless functions" }, "rsvps": [ { "id": 1, "name": "Welcome Dinner", "description": null, "datetime": "2030-09-20T17:00:00.000Z", "location": null, "response": true }, { "id": 2, "name": "Wedding", "description": null, "datetime": "2030-09-21T16:30:00.000Z", "location": null, "response": true }, { "id": 3, "name": "Farewell Brunch", "description": null, "datetime": "2030-09-22T10:00:00.000Z", "location": null, "response": true } ] }
Which can then be easily leveraged in my TSX component, like so:

Production Deployment
When I created my Vercel Postgres database earlier, I had to connect it to a Vercel project (aka site).
Upon connection, Vercel automatically ensured all necessary Postgres environment variables were made available to website's production, preview, and dev environments:

From there, my deploy completed without a mishap β .
Closing Thoughts
I was extremely impressed with Vercel's developer experience... it genuinely was so easy getting a database stood up and hooked up to my application within minutes.
The only things I'm left wondering about are...
-
Is there an easy way to protect a site behind a password using Next.js and Vercel?
I see that Vercel offers password protection as part of their Pro Plan... ($150 per month). I think I'll pass on that.
-
Is it kind of odd that I'm using SQL directly via my serverless function, instead of some sort of API?
I suppose, given the size of my site, I'm not aiming for blazing fast speed... However, I can see why this makes Supabase a compelling choice for develompent, as they auto-generate APIs for you right OOTB.
-
Registries...
How the heck is that gonna work? I wonder if I could just create a registry using Zola or The Knot... and then hook that up to my site manually. If anyone knows, please shoot me a DM. Or, leave a comment... once I make it possible to leave comments on my blog posts π
Thank you very much for reading, and I hope you enjoy the wedding website.
