BackHome

Full Setup Guide

Documentation

A complete step-by-step guide to install, configure, and deploy your cinematic portfolio using Next.js, Supabase, and Resend.

1. Getting Started

Clone the repository and install all dependencies. This project uses Next.js App Router, Tailwind CSS, GSAP, and Supabase.

git clone https://github.com/Saarangggg/nextjs-cinematic-portfolio.git
cd nextjs-cinematic-portfolio
npm install

After install, the project requires environment variables before it can run. Continue to the next steps.

2. Supabase Setup

Your portfolio gallery, admin dashboard, analytics, and contact data are all powered by Supabase — a free open-source Firebase alternative.

Step 1 — Create Account

Go to supabase.com, click Start your project, and sign in with GitHub or Email.

Step 2 — Create New Project

FieldExample
Project Namesarang-portfolio
Database PasswordUse a strong password
RegionMumbai / Singapore

Step 3 — Copy API Keys

Go to Project Settings → API and copy these three values:

  • Project URLNEXT_PUBLIC_SUPABASE_URL
  • Anon/Public KeyNEXT_PUBLIC_SUPABASE_ANON_KEY
  • Service Role KeySUPABASE_SERVICE_ROLE_KEY
🚫 ImportantNever expose the Service Role Key publicly or commit it to GitHub.

3. Create Database Tables

Go to Supabase Dashboard → SQL Editor → New Query, paste the SQL below, and click Run.

-- ── Works (portfolio gallery) ──────────────────
CREATE TABLE IF NOT EXISTS works (
    id          UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    title       TEXT        NOT NULL,
    description TEXT        NOT NULL DEFAULT '',
    category    TEXT        NOT NULL DEFAULT 'Website',
    tech        TEXT        NOT NULL DEFAULT '',
    image_url   TEXT        NOT NULL DEFAULT '',
    link        TEXT        NOT NULL DEFAULT '',
    sort_order  INTEGER     NOT NULL DEFAULT 0,
    visible     BOOLEAN     NOT NULL DEFAULT TRUE,
    gallery     JSONB       NOT NULL DEFAULT '[]',
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ── Projects (case-study pages) ─────────────────
CREATE TABLE IF NOT EXISTS projects (
    id          UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    title       TEXT        NOT NULL,
    description TEXT        NOT NULL DEFAULT '',
    category    TEXT        NOT NULL DEFAULT 'Website',
    tech        TEXT        NOT NULL DEFAULT '',
    image_url   TEXT        NOT NULL DEFAULT '',
    link        TEXT        NOT NULL DEFAULT '',
    sort_order  INTEGER     NOT NULL DEFAULT 0,
    visible     BOOLEAN     NOT NULL DEFAULT TRUE,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ── Inquiries (contact form) ─────────────────────
CREATE TABLE IF NOT EXISTS inquiries (
    id         UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    name       TEXT        NOT NULL,
    email      TEXT        NOT NULL DEFAULT '',
    message    TEXT        NOT NULL DEFAULT '',
    ip         TEXT        NOT NULL DEFAULT '',
    read       BOOLEAN     NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ── Reviews (testimonials) ───────────────────────
CREATE TABLE IF NOT EXISTS reviews (
    id         UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    author     TEXT        NOT NULL,
    role       TEXT        NOT NULL DEFAULT '',
    company    TEXT        NOT NULL DEFAULT '',
    content    TEXT        NOT NULL,
    rating     INTEGER     NOT NULL DEFAULT 5,
    avatar     TEXT        NOT NULL DEFAULT '',
    approved   BOOLEAN     NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ── Visits (page analytics) ─────────────────────
CREATE TABLE IF NOT EXISTS visits (
    id         UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    page       TEXT        NOT NULL DEFAULT '/',
    referrer   TEXT        NOT NULL DEFAULT '',
    userAgent  TEXT        NOT NULL DEFAULT '',
    ip         TEXT        NOT NULL DEFAULT '',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ── Ad Clicks (link tracking) ───────────────────
CREATE TABLE IF NOT EXISTS ad_clicks (
    id         UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    label      TEXT        NOT NULL,
    url        TEXT        NOT NULL,
    page       TEXT        NOT NULL DEFAULT '/',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ── Searches (search tracking) ──────────────────
CREATE TABLE IF NOT EXISTS searches (
    id            UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    query         TEXT        NOT NULL UNIQUE,
    count         INTEGER     NOT NULL DEFAULT 1,
    last_searched TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ── Settings (social links, config) ─────────────
CREATE TABLE IF NOT EXISTS settings (
    key   TEXT  PRIMARY KEY,
    value JSONB NOT NULL DEFAULT '{}'
);

INSERT INTO settings (key, value) VALUES
    ('social_links', '{"instagram":"","github":"","linkedin":"","twitter":"","youtube":"","behance":"","dribbble":"","whatsapp":""}'),
    ('coming_soon',  'false')
ON CONFLICT (key) DO NOTHING;

-- ── Row Level Security ───────────────────────────
ALTER TABLE works     ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects  ENABLE ROW LEVEL SECURITY;
ALTER TABLE inquiries ENABLE ROW LEVEL SECURITY;
ALTER TABLE reviews   ENABLE ROW LEVEL SECURITY;
ALTER TABLE visits    ENABLE ROW LEVEL SECURITY;
ALTER TABLE ad_clicks ENABLE ROW LEVEL SECURITY;
ALTER TABLE searches  ENABLE ROW LEVEL SECURITY;
ALTER TABLE settings  ENABLE ROW LEVEL SECURITY;

CREATE POLICY "works_public_read"       ON works      FOR SELECT USING (visible = TRUE);
CREATE POLICY "works_service_all"       ON works      USING (auth.role() = 'service_role');
CREATE POLICY "projects_public_read"    ON projects   FOR SELECT USING (visible = TRUE);
CREATE POLICY "projects_service_all"    ON projects   USING (auth.role() = 'service_role');
CREATE POLICY "inquiries_public_insert" ON inquiries  FOR INSERT WITH CHECK (TRUE);
CREATE POLICY "inquiries_service_all"   ON inquiries  USING (auth.role() = 'service_role');
CREATE POLICY "reviews_public_read"     ON reviews    FOR SELECT USING (approved = TRUE);
CREATE POLICY "reviews_service_all"     ON reviews    USING (auth.role() = 'service_role');
CREATE POLICY "visits_public_insert"    ON visits     FOR INSERT WITH CHECK (TRUE);
CREATE POLICY "visits_service_all"      ON visits     USING (auth.role() = 'service_role');
CREATE POLICY "ad_clicks_public_insert" ON ad_clicks  FOR INSERT WITH CHECK (TRUE);
CREATE POLICY "ad_clicks_service_all"   ON ad_clicks  USING (auth.role() = 'service_role');
CREATE POLICY "searches_public_insert"  ON searches   FOR INSERT WITH CHECK (TRUE);
CREATE POLICY "searches_service_all"    ON searches   USING (auth.role() = 'service_role');
CREATE POLICY "settings_public_read"    ON settings   FOR SELECT USING (TRUE);
CREATE POLICY "settings_service_all"    ON settings   USING (auth.role() = 'service_role');

-- ── Storage Bucket (for image uploads) ──────────
INSERT INTO storage.buckets (id, name, public)
VALUES ('project-photos', 'project-photos', TRUE)
ON CONFLICT (id) DO NOTHING;

CREATE POLICY "photos_public_read"    ON storage.objects FOR SELECT USING (bucket_id = 'project-photos');
CREATE POLICY "photos_service_upload" ON storage.objects FOR INSERT WITH CHECK (bucket_id = 'project-photos' AND auth.role() = 'service_role');
CREATE POLICY "photos_service_delete" ON storage.objects FOR DELETE USING (bucket_id = 'project-photos' AND auth.role() = 'service_role');

4. Resend Email Setup

The contact form uses Resend to deliver messages directly to your inbox.

  1. Go to resend.com and sign up with GitHub.
  2. Go to Dashboard → API Keys → Create API Key.
  3. Set Name: Portfolio, Permission: Full Access.
  4. Copy the key — it starts with re_ — into your .env.local.
  5. Set ADMIN_EMAIL to the email address where you want to receive notifications.

5. Environment Variables

Create a file called .env.local in the root of your project and paste the following. Fill in each value.

# ─────────────────────────────────────────────
# SARANG PORTFOLIO — ENVIRONMENT VARIABLES
# NEVER SHARE THIS FILE OR COMMIT TO GITHUB
# ─────────────────────────────────────────────

# ── SUPABASE ──────────────────────────────
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

# ── RESEND ────────────────────────────────
RESEND_API_KEY=re_your_api_key

# ── ADMIN AUTH ────────────────────────────
ADMIN_PASSWORD=choose-a-strong-password

# ── JWT SECRET ────────────────────────────
JWT_SECRET=your-super-secret-random-string

# ── CONTACT EMAIL ─────────────────────────
ADMIN_EMAIL=you@example.com

# ── WEBSITE URL ───────────────────────────
NEXT_PUBLIC_SITE_URL=https://yourdomain.com

# ── WHATSAPP NUMBER ───────────────────────
NEXT_PUBLIC_WHATSAPP_NUMBER=919876543210

# ── SEO VERIFICATION ──────────────────────
NEXT_PUBLIC_GOOGLE_VERIFICATION=google-code
NEXT_PUBLIC_BING_VERIFICATION=bing-code
⚠ WarningAdd .env.local to your .gitignore to keep it private.

6. Generate JWT Secret

The admin login uses JWT. Generate a secure random string and paste it as JWT_SECRET.

Mac / Linux:

openssl rand -base64 32

Windows PowerShell:

[System.Web.Security.Membership]::GeneratePassword(32,4)

7. Run Development Server

Start the local dev server. Open your browser at http://localhost:3000.

npm run dev

8. Admin Dashboard

The hidden admin panel is at /admin/login. Use the password from ADMIN_PASSWORD in your .env.local.

Add & Edit Projects
Manage Gallery & Categories
Read Contact Messages
View Analytics & Visits
Track Link Clicks
Manage Reviews

9. Adding Projects

No code needed. Everything goes through the Admin Dashboard.

  1. Log in at /admin/login.
  2. Go to Projects → Add New Project.
  3. Fill in the fields:
FieldExample
TitleNike Concept
DescriptionCinematic branding project
CategoryWebsite / Designs / Photos / Videos
Image URLhttps://...
Project Linkhttps://...

The project instantly appears in the frontend gallery after saving.

10. Website Structure

Hero

3D intro animations, GSAP cinematic transitions, smooth scroll.

About

Edit from src/app/about/content.js — bio, tech stack, experience, resume.

Work Gallery

Interactive WebGL circular gallery, powered by Supabase. Categories: Websites, Designs, Photos, Videos.

Contact

Connected to Resend email API and Supabase messages table.

Admin /admin/login

Hidden dashboard to manage all content without touching code.

11. Editing Your Personal Info

Replace all demo content with your own. Use global search in your editor — Ctrl + Shift + F (Windows) or Cmd + Shift + F (Mac) — and search these keywords one by one:

Search ForReplace With
SarangYour name
example@gmail.comYour email
919876543210Your WhatsApp number
yourdomain.comYour website URL
sarang-space.siteYour live domain

Important files to edit directly:

src/app/about/content.js

Name, bio, skills, experience, resume URL

src/app/layout.js

SEO title, description, OpenGraph, keywords

.env.local

Email, WhatsApp number, domain URL

public/

Hero video, background images, portrait photos, logos

12. Google Search Console

  1. Go to Google Search Console and add your domain.
  2. Choose HTML Tag verification. Copy only the content value from the meta tag.
  3. Paste it into .env.local as NEXT_PUBLIC_GOOGLE_VERIFICATION=xxxx.
  4. After deployment, submit your sitemap: https://yourdomain.com/sitemap.xml.

13. Bing Webmaster Tools

  1. Go to Bing Webmaster Tools.
  2. Add your website and copy the verification code.
  3. Paste into .env.local as NEXT_PUBLIC_BING_VERIFICATION=xxxx.

14. Deploy to Vercel

This portfolio is optimized for Vercel — the same company that makes Next.js.

  1. Push your code to a new GitHub repository: git push.
  2. Go to vercel.com, click Add New → Project.
  3. Import your GitHub repo.
  4. Under Environment Variables, paste all keys from your .env.local.
  5. Click Deploy. Done — your portfolio is live.
ℹ NoteAfter deployment, add your live domain to Supabase's allowed URLs under Project Settings → Authentication → URL Configuration.

15. Security Notes

Never upload or commit .env.local to GitHub.
Never expose the Supabase Service Role Key publicly.
Use a strong, unique ADMIN_PASSWORD.
Enable Row Level Security (RLS) in Supabase for all tables.
Use HTTPS domain only — never HTTP in production.
Rotate your JWT_SECRET periodically.

Built for creators, designers, filmmakers, and developers. This portfolio is fully open-source and customizable.

Back to Open Source