AirDish
A modern restaurant & food website template with a built-in admin dashboard, online reservations, dynamic menu, blog, and image management.
Thank you for purchasing AirDish. This guide walks you through everything you need to install, configure, and launch your new restaurant website โ written so that anyone can follow it, even if this is your first time working with a Next.js project. Please read the steps in order.
Quick Start (TL;DR)
If you are already comfortable with Node.js and PostgreSQL, here is the entire setup in five commands. Detailed explanations follow below.
# 1. Install dependencies
npm install
# 2. Create your .env file from the template, then fill in DATABASE_URL,
# DIRECT_URL, ADMIN_PASSWORD, EMAIL_ENCRYPTION_KEY (and S3 keys if you want uploads)
cp .env.example .env
# 3. Generate Prisma client + create all database tables
npx prisma generate
npx prisma migrate deploy
# 4. (Optional) Load demo content so the site doesn't look empty
npm run seed
# 5. Start the development server
npm run dev
Open http://localhost:3000
for the site and http://localhost:3000/admin to sign in.
EMAIL_ENCRYPTION_KEY? Generate one with
openssl rand -base64 32 and paste the output into your .env. It is required for the
email subsystem (reservation confirmation emails).
1. Introduction
AirDish is a complete, production-ready website for restaurants, cafรฉs, and food businesses. Unlike a static HTML template, AirDish is a full-stack application โ it comes with a password-protected admin dashboard where you can manage your menu, categories, blog posts, reservations, FAQs, and homepage content without touching any code.
Visitors can browse your menu, read your blog, subscribe to your newsletter, and book a table online. Every booking and subscription is stored safely in your database and is instantly visible in the admin dashboard.
2. Features
๐ Modern Landing Page
Animated single-page design: Hero, About, Menu, Categories, Testimonials, Reservation, FAQ, and Blog sections.
๐ Admin Dashboard
Secure, password-protected control panel at /admin to manage all site content.
๐ Online Reservations
Guests book a specific table for a date and time window, optionally pre-ordering menu items. Confirm/cancel and export to CSV.
๐ช Table Management
Define your real-world tables (name + seats). The booking form checks live availability per table and time window.
๐ฝ๏ธ Dynamic Menu & Categories
Add, edit, reorder, and hide menu items and food categories at any time.
โญ Customer Testimonials
Visitors leave reviews on menu items; you approve, reject, reorder, and feature them on the homepage.
โ๏ธ Reservation Emails
Automatic confirmation emails via Mailtrap (Resend/SendGrid/SES/SMTP ready), with an idempotent audit log of every send.
โ๏ธ Blog with Rich Editor
Write posts with a built-in rich-text editor, tags, cover images, and SEO-friendly URLs.
๐ง Newsletter Capture
Collect subscriber emails from the site and view them in the dashboard.
๐ผ๏ธ Cloud Image Uploads
Upload images directly to Amazon S3 (or compatible) storage from the admin panel.
๐ฑ Fully Responsive
Looks great on desktops, tablets, and phones out of the box.
3. Technology Stack
AirDish is built with modern, well-supported technologies:
| Technology | Purpose |
|---|---|
| Next.js 16 (App Router) | The core framework that powers the website and its server features. |
| React 19 | Builds the interactive user interface. |
| TypeScript | Adds type safety for reliable, maintainable code. |
| Tailwind CSS 4 | Utility-first styling for the entire site. |
| Ant Design 6 | UI components used in the admin dashboard. |
| Framer Motion | Smooth animations and transitions. |
| Tiptap | The rich-text editor for writing blog posts. |
| Prisma 7 + PostgreSQL | Database layer that stores all your content, reservations, and email logs. |
| Zod | Runtime validation for environment variables and API payloads. |
| AWS S3 (or any S3-compatible service) | Cloud storage for uploaded images. |
| Mailtrap (default email provider) | Sends reservation confirmation emails. Schema also supports Resend, SendGrid, SES, and generic SMTP. |
4. Requirements
Before you begin, make sure you have the following ready:
- Node.js 20 or newer โ download from nodejs.org. (This project was built and tested on Node
v20.19.3.) - A PostgreSQL database โ a free Neon account works perfectly and is recommended. Any PostgreSQL provider (Supabase, Railway, AWS RDS, or a local install) also works.
- An Amazon S3 bucket (or S3-compatible storage like Cloudflare R2, DigitalOcean Spaces, or MinIO) โ required only if you want to upload images through the admin dashboard.
- A code editor such as Visual Studio Code (optional, but helpful).
- Basic comfort using a terminal / command line.
node -v in your terminal. If it prints a
number lower than 20, please update Node.js first.
5. Folder Structure
Here is an overview of the most important files and folders so you know where everything lives:
airdish/
โโโ prisma/
โ โโโ schema.prisma # Database structure (tables & fields)
โ โโโ migrations/ # Database version history
โ โโโ seed.ts # Optional sample-data script (run: npm run seed)
โโโ public/
โ โโโ images/ # Default demo images
โ โโโ themes/airdish/ # Brand logo & favicon
โ โโโ *.svg # Icons
โโโ scripts/
โ โโโ migrate-sqlite-to-neon.ts # One-off helper (legacy data migration)
โโโ src/
โ โโโ app/
โ โ โโโ page.tsx # The homepage
โ โ โโโ layout.tsx # Site-wide layout, fonts & theme color
โ โ โโโ globals.css # Global styles
โ โ โโโ admin/ # The admin dashboard (/admin)
โ โ โโโ blog/ # Blog post pages (/blog/...)
โ โ โโโ api/ # Backend endpoints (menu, reservations, tables, ...)
โ โโโ components/ # Homepage sections (Hero, Menu, Footer...)
โ โโโ lib/ # DB client, env validation, crypto, defaults
โ โโโ server/ # Backend services (email, reservations)
โ โโโ generated/ # Auto-generated Prisma client (do not edit)
โโโ .env # YOUR private settings (you create this)
โโโ .env.example # Template for the .env file (local / Neon)
โโโ .env.production.example # Template for self-hosted production deploys
โโโ next.config.ts # Next.js configuration (allowed image domains)
โโโ prisma.config.ts # Prisma CLI configuration
โโโ package.json # Dependencies & scripts
โโโ docs/ # This documentation
.env file. Everything else can be
managed from the admin dashboard once the site is running.
6. Step-by-Step Setup
Follow these steps in order. Open a terminal inside the project folder (the folder that contains
package.json) before you start.
-
Install the dependencies
This downloads all the libraries the project needs. It may take a couple of minutes.
npm install -
Create your environment file
Copy the provided template to a new file named
.env, then open it in your editor. You will fill in the values in the next section.# macOS / Linux cp .env.example .env # Windows (Command Prompt) copy .env.example .env -
Fill in your settings
Edit the
.envfile with your database and storage details. See Environment Variables below for what each value means. -
Generate the database client
This prepares Prisma to talk to your database.
npx prisma generate -
Create the database tables
This builds all the tables your site needs inside your PostgreSQL database.
npx prisma migrate deploy -
(Optional) Load sample data
Skip this if you want to start completely empty. Otherwise, populate menu items, categories, blog posts, FAQs, tables, and demo testimonials so you can immediately see how the site looks with content:
npm run seed -
Start the website
Launch the development server and open the site in your browser.
npm run devNow visit
http://localhost:3000to see your site, andhttp://localhost:3000/adminto log in to the dashboard.
ADMIN_PASSWORD and start replacing the demo content with your own.
7. Environment Variables
The .env file holds all your private settings. Never share this file or commit it
publicly โ it contains your passwords and keys. Below is the full template with safe placeholder
values:
# ---- Database (PostgreSQL / Neon) ----
DATABASE_URL="postgresql://USER:[email protected]/DBNAME?sslmode=require&pgbouncer=true&connect_timeout=15"
DIRECT_URL="postgresql://USER:[email protected]/DBNAME?sslmode=require"
# ---- Admin dashboard password ----
ADMIN_PASSWORD="choose-a-strong-password"
# ---- Email subsystem (required to send reservation confirmations) ----
# Generate with: openssl rand -base64 32
EMAIL_ENCRYPTION_KEY="your-32-byte-base64-key"
# Optional fallback sender used until you save EmailSettings in /admin โ Settings
EMAIL_FALLBACK_SENDER_EMAIL="[email protected]"
EMAIL_FALLBACK_SENDER_NAME="Your Restaurant"
# Brand bits embedded in the email template
RESTAURANT_NAME="Your Restaurant"
RESTAURANT_SUPPORT_EMAIL="[email protected]"
# ---- Image storage (Amazon S3 or compatible) ----
AWS_ACCESS_KEY_ID="your-access-key"
AWS_SECRET_ACCESS_KEY="your-secret-key"
AWS_REGION="your-region"
AWS_BUCKET="your-bucket-name"
NEXT_PUBLIC_S3BUCKET="https://your-cdn-or-bucket-domain"
| Variable | What it is | Required? |
|---|---|---|
DATABASE_URL |
The pooled connection string your website uses to read/write data. From Neon, use the pooled connection. | Yes |
DIRECT_URL |
A direct (non-pooled) connection string used only when creating/changing database tables. From Neon,
use the direct connection. (Can be the same value as DATABASE_URL for non-pooled
setups.) |
Yes |
ADMIN_PASSWORD |
The password you'll type to log in to /admin. Choose something strong and unique. |
Yes |
EMAIL_ENCRYPTION_KEY |
Base64-encoded 32-byte key used to encrypt the email provider API token at rest. Generate once with
openssl rand -base64 32 and keep it constant โ rotating it invalidates saved tokens.
|
For email |
EMAIL_FALLBACK_SENDER_EMAIL |
"From" address used if the admin has not yet saved a sender in /admin โ Settings. |
Optional |
EMAIL_FALLBACK_SENDER_NAME |
"From" name to match the fallback email. | Optional |
RESTAURANT_NAME |
Restaurant name shown inside reservation emails. Defaults to "AirDish". | Optional |
RESTAURANT_SUPPORT_EMAIL |
Support address shown in email footers. | Optional |
AWS_ACCESS_KEY_ID |
Access key for your S3 storage account. | For uploads |
AWS_SECRET_ACCESS_KEY |
Secret key for your S3 storage account. | For uploads |
AWS_REGION |
The region your bucket lives in, e.g. ap-southeast-1. |
For uploads |
AWS_BUCKET |
The name of your storage bucket. | For uploads |
NEXT_PUBLIC_S3BUCKET |
The public base URL/endpoint where uploaded images are served from. | For uploads |
ADMIN_PASSWORD with a strong password and generate a brand-new
EMAIL_ENCRYPTION_KEY before going live โ never reuse the demo values.
8. Database Setup
AirDish needs a PostgreSQL database. You have two common options โ pick whichever fits your hosting plan:
Option A โ Hosted database (Neon, recommended for most buyers)
Neon is a free, managed PostgreSQL service that takes about two minutes to set up. Supabase, Railway, AWS RDS, and Vercel Postgres all work the same way.
-
Create a database
Sign up at neon.tech, click New Project, pick the closest region, and accept the defaults. Neon will create a database for you.
-
Copy the two connection strings
On the Neon dashboard click Connection Details. Copy the Pooled connection string into
DATABASE_URL, and the Direct connection string intoDIRECT_URLin your.envfile. Keep the?sslmode=requiresuffix. -
Create the tables
Run the migration. This builds every table the site needs:
npx prisma migrate deploy
Option B โ Self-hosted PostgreSQL (VPS / dedicated server)
If you are deploying to your own Linux server (Ubuntu, Debian, etc.), you can install PostgreSQL locally and skip the hosted provider.
-
Install PostgreSQL
# Ubuntu / Debian sudo apt update sudo apt install -y postgresql postgresql-contrib -
Create a database and user
sudo -u postgres psql -- Inside psql: CREATE DATABASE airdish; CREATE USER airdish_user WITH ENCRYPTED PASSWORD 'CHOOSE-A-STRONG-PASSWORD'; GRANT ALL PRIVILEGES ON DATABASE airdish TO airdish_user; \q -
Set both URLs to the same local connection
In
.env(or.env.production.localon the server), use:DATABASE_URL="postgresql://airdish_user:[email protected]:5432/airdish?sslmode=disable" DIRECT_URL="postgresql://airdish_user:[email protected]:5432/airdish?sslmode=disable"Use127.0.0.1, not a LAN IP like192.168.x.x. The production host can't reach a private LAN address โ every DB query will return 500. -
Create the tables
npx prisma migrate deploy
Viewing your data (optional)
Prisma includes a friendly visual database browser. Run this and open the URL it prints:
npx prisma studio
Loading demo content (optional)
If you want the homepage to look populated immediately, run the included seed script. It inserts demo menu items, categories, blog posts, FAQs, tables, and testimonials โ safe to run multiple times.
npm run seed
9. Running the Project
The project includes a few simple commands (defined in package.json):
| Command | What it does |
|---|---|
npm run dev |
Starts the development server at http://localhost:3000. Use this while
building/previewing your site. |
npm run build |
Runs prisma generate then builds an optimized production bundle. Run before deploying.
|
npm start |
Runs the production build locally (run npm run build first). |
npm run seed |
Loads demo content (menu, categories, blog, FAQs, tables, testimonials). Idempotent โ safe to re-run. |
npm run lint |
Checks the code for common issues. |
npm run type-check |
Runs TypeScript without emitting files, to catch type errors. |
npx prisma studio |
Opens a visual database browser in your web browser. |
npx prisma migrate deploy |
Applies any pending database migrations. Run after pulling updates or on first install. |
npm run dev. When you're ready to publish, use
npm run build followed by npm start (or deploy to a host โ see Deployment).
10. Admin Dashboard
The admin dashboard is the heart of AirDish. Visit /admin (for example
http://localhost:3000/admin) and log in using the ADMIN_PASSWORD you set in your
.env file.
What you can manage
| Section | What you can do |
|---|---|
| Reservations | View all table bookings (with the chosen table, time window, and any pre-ordered dishes). Confirm or cancel them โ a confirmation email is sent automatically when you confirm, if email is configured. Export everything to CSV. |
| Menu Items | Add dishes with name, price, original price, and photo. Reorder, hide, or delete them. |
| Categories | Manage food categories shown on the homepage. |
| Tables | Define your real tables (name + seat count). The public reservation form uses this to check live availability per table and time window, so two parties can't book the same table at once. |
| Blog Posts | Write and publish articles using the rich-text editor, with cover image, author, tags, and read time. |
| FAQs | Add and reorder frequently asked questions. |
| Testimonials | Moderate customer reviews: approve, reject, reorder, feature on the homepage, and edit. Each review is tied to a specific menu item. |
| Newsletter | View everyone who subscribed to your newsletter. |
| Hero Content | Edit the big headline, description, and main image at the top of the homepage. |
| About Content | Edit the "About" section text, images, and statistics. |
| Settings | Configure the email provider used to send reservation confirmations (see Email setup below). Send a test email to verify the configuration. |
.env) if you ever suspect it has been exposed.
11. Email (Reservation Confirmations)
When you confirm a reservation in the admin dashboard, AirDish sends the guest an automatic confirmation email containing the date, time, table, and pre-ordered items (if any). Email is optional โ the site works fine without it โ but enabling it is a nice touch for your guests.
Step 1 โ Set the encryption key (one-time)
The email provider's API token is stored in your database encrypted at rest, so a database
leak alone cannot expose it. You must provide the encryption key in your .env file:
# Generate this once, then keep it constant across deploys
openssl rand -base64 32
Copy the output and paste it as:
EMAIL_ENCRYPTION_KEY="paste-the-output-here"
Step 2 โ Pick an email provider
The bundled provider is Mailtrap (free tier
available). The schema also supports Resend, SendGrid, SES, and generic SMTP โ each requires a small provider
class to be added to src/server/email/providers/. For most ThemeForest buyers, Mailtrap is the
fastest path.
-
Create a Mailtrap account
Sign up at mailtrap.io. For real customer emails use a Sending Domain (Email API โ Sending Domains), not the sandbox inbox.
-
Copy your API token
Mailtrap โ Sending Domains โ API and SMTP โ copy the API Token.
-
Save the settings in the admin dashboard
Sign in to
/admin, go to Settings, and fill in:- Provider: Mailtrap
- API Token: paste the token from Mailtrap
- Sender Email: an address on your verified Mailtrap sending domain (e.g.
[email protected]) - Sender Name: your restaurant name as guests should see it
Click Save, then Send Test Email to verify everything works.
How sending works
- Emails go out automatically when an admin marks a reservation as Confirmed.
- Every send attempt is recorded in the
EmailLogtable (status:PENDING/SENT/FAILED/SKIPPED) so you have a full audit trail. - Each attempt uses a unique idempotency key โ clicking "Confirm" twice will not send two emails.
- If a reservation was submitted without an email address, the system records
SKIPPEDand does not send.
12. Image Uploads (S3)
When you upload an image in the admin dashboard, AirDish stores it in your S3 bucket and saves the public
link. To enable this, fill in the five AWS_* / NEXT_PUBLIC_S3BUCKET values in your
.env file.
Upload rules
- Allowed formats: JPEG, PNG, WebP, GIF, and AVIF.
- Maximum size: 5 MB per image.
Displaying images from other domains
For security, Next.js only displays optimized images from approved domains. If your images are hosted
somewhere new, add that domain to next.config.ts:
const nextConfig = {
images: {
remotePatterns: [
{ protocol: "https", hostname: "images.unsplash.com" },
{ protocol: "https", hostname: "ds.manob.ai" },
// Add your own image/CDN domain here:
{ protocol: "https", hostname: "your-cdn-or-bucket-domain" },
],
},
};
NEXT_PUBLIC_S3BUCKET at its endpoint and use that provider's
keys.
13. Customization
Brand / accent color
The site's signature orange is set in one place. Open src/app/layout.tsx and change
colorPrimary:
theme={{
token: {
colorPrimary: "#f97316", // โ change to your brand color
borderRadius: 8,
},
}}
Site title & description (SEO)
Edit the metadata object in src/app/layout.tsx:
export const metadata = {
title: "Your Restaurant Name โ A Culinary Journey",
description: "Your own description here",
};
Homepage content
Most homepage content (Hero, About, Menu, Categories, Blog, FAQ) is edited from the admin
dashboard โ no coding required. To rearrange or remove whole sections, edit
src/app/page.tsx, where each section is a clearly named component.
Fonts
AirDish uses Inter and Playfair Display from Google Fonts, configured at the top of
src/app/layout.tsx. Swap them for any Google Font there.
Demo images
Default placeholder images live in public/images/. Replace these files (keeping the same names)
to change demo visuals, or upload your own through the admin dashboard.
14. Deployment
AirDish is a standard Next.js app and can be hosted anywhere that supports Node.js. Vercel (made by the creators of Next.js) is the simplest option.
Deploying to Vercel
-
Push your project to a Git provider
Upload the project to GitHub, GitLab, or Bitbucket.
-
Import it into Vercel
In Vercel, click "New Project" and select your repository.
-
Add your environment variables
Copy every value from your
.envfile into Vercel's Environment Variables settings. -
Deploy
Vercel runs
npm run buildautomatically and publishes your live site.
Deploying to your own server (VPS / dedicated)
The project ships with a separate template, .env.production.example, for deploys where Postgres
runs on the same machine as Node. On the server:
# 1. Pull / upload the project, then:
npm install --omit=dev
cp .env.production.example .env.production.local
# Edit .env.production.local โ use 127.0.0.1 for the DB host, NOT a 192.168.* IP
# 2. Set up the database (if you haven't already โ see Option B above)
npx prisma migrate deploy
# 3. Build & run
npm run build
npm start
For long-running deploys, run npm start under a process manager such as PM2 or a systemd service, and put Nginx
in front for HTTPS.
- A strong
ADMIN_PASSWORD. - A freshly generated
EMAIL_ENCRYPTION_KEY(don't reuse the dev key). - Correct production database URLs (use
127.0.0.1for local Postgres, not a LAN IP). - Real S3 credentials and your image domain listed in
next.config.ts. - Email provider saved in
/admin โ Settingsand a successful test email.
15. Troubleshooting
"DATABASE_URL is not set"
Your .env file is missing or the DATABASE_URL line is empty. Make sure the file is
named exactly .env (not .env.txt), is in the project root, and contains a valid
connection string. Restart the server after editing.
Can't connect to the database / timeout
Check that the connection string is correct and that your database allows connections. For Neon, confirm you
used the pooled URL for DATABASE_URL and the direct URL for
DIRECT_URL.
"Table does not exist" errors
You haven't created the tables yet. Run npx prisma migrate deploy.
Admin login fails
The password you typed must match ADMIN_PASSWORD in .env exactly. After changing
it, restart the server.
Image upload fails
Verify all five S3 settings are filled in correctly, the file is under 5 MB, and it's one of the allowed formats (JPEG, PNG, WebP, GIF, AVIF).
An uploaded image won't display
Add the image's domain to remotePatterns in next.config.ts (see Image Uploads), then restart.
Changes to .env aren't taking effect
Environment changes only load when the server starts. Stop the server (Ctrl + C) and run
npm run dev again.
"Email encryption key is not configured"
You haven't set EMAIL_ENCRYPTION_KEY in .env. Generate one with
openssl rand -base64 32, paste it in, then restart the server. See Email
setup.
"EMAIL_ENCRYPTION_KEY must decode to exactly 32 bytes"
The value you pasted isn't valid base64 or isn't 32 bytes. Re-generate it with
openssl rand -base64 32 and paste the entire output, including any trailing =.
Test email fails / "Failed to send"
Open /admin โ Settings and re-check the provider token, sender email, and that the sender
address is on a verified Mailtrap sending domain. The error message returned to the admin UI is sanitised โ
full provider errors are written to the server log.
Reservation form says "No tables available"
Go to /admin โ Tables and add at least one table with a seat count. The reservation form filters
tables by guest count and time window โ if every table is already booked for that window, none will be
selectable.
Production site returns 500 on every page
Almost always a database connection problem. On a self-hosted production server, make sure
DATABASE_URL uses 127.0.0.1 (or your DB hostname), not a
192.168.x.x LAN IP from your development network โ the production host cannot reach it.
16. Sources & Credits
This template was built using these excellent open-source projects and resources:
- Next.js โ React framework
- React โ UI library
- Tailwind CSS โ styling
- Ant Design โ admin UI components
- Framer Motion โ animations
- Tiptap โ rich-text editor
- Prisma โ database toolkit
- Zod โ schema validation
- Lucide โ icons
- Mailtrap โ transactional email (default provider)
- Amazon S3 โ image storage
- Google Fonts โ Inter & Playfair Display
17. Support
If you run into a problem this guide doesn't cover:
- Re-read the Troubleshooting section โ most issues are listed there.
- Confirm your
.envvalues are correct and the server was restarted after changes. - Make sure you completed every step in Step-by-Step Setup, in order.
- Reach out through your ThemeForest item's Support tab with a clear description of the issue and any error message you see.
Thank you again for choosing AirDish. We hope it serves your business beautifully. ๐ฝ๏ธ