Not a template. Every pixel is hand-authored.
The site doubles as a portfolio and a live demo — it runs a real AI-powered build
planner at /build-together so recruiters,
investors, and fellow builders can experience the kind of product I ship instead of
reading about it.
Three audiences, one URL. The ?mode= parameter swaps the hero, credentials, and
stack detail for General / Recruiters / Investors. Deep-linkable, SEO-indexed
separately, zero SPA overhead.
Personal site of Rahul Babu — patent-published AI inventor, founder of Laundroswipe (600+ users), Harvard HPAIR delegate. Building since age 8.
| AI-powered build planner | 5-question interactive quiz at /build-together that drafts a personalised 30-day build plan. Default path: deterministic client-side generator (zero cost, privacy-safe). Optional BYOK: paste your own API key for a real streamed response. |
| Force-directed constellation | Hero canvas is a physics-driven graph of 12 years of projects. Drag nodes, hover for details. Pure canvas — no WebGL, no dependencies, 60fps on mobile. |
| Live contact form | Server-validated, IP rate-limited (6/h), honeypotted, written to Supabase via service-role (anon key never touches browser). Optional Resend fan-out for email notifications. |
| Short-URL redirects | rahulbk.com/calendar, /github, /linkedin, /book, /cv all redirect cleanly via vercel.json. Every link is memorable. |
| AEO / GEO optimised | Robots allowlist for GPTBot, ClaudeBot, PerplexityBot, Google-Extended. Person + WebSite + ProfessionalService + FAQPage + BreadcrumbList JSON-LD for answer-engine surfacing. llms.txt shipped for AI crawler guidance. |
| Konami egg → source | Type ↑↑↓↓←→←→BA anywhere on the page. |
| Build-time PNGs | og.png, favicon-32.png, apple-touch-icon.png regenerated from SVG source on every Vercel build via sharp. Git tracks only the SVGs. |
Pure static HTML + one Node serverless function. No framework, no bundler, no hydration. First paint < 500ms on 4G.
.
├── public/ STATIC ROOT — served as the website
│ ├── index.html Portfolio (three audience modes)
│ ├── build-together.html AI-powered build planner
│ ├── thanks.html Contact form success page
│ ├── og.svg → og.png Social preview, regenerated each build
│ ├── favicon.svg → *.png Favicons, regenerated each build
│ ├── manifest.webmanifest PWA-lite
│ ├── robots.txt / sitemap.xml SEO
│ └── llms.txt AI crawler guidance (AEO/GEO)
├── api/
│ └── contact.js POST → validate → rate-limit → Supabase → (optional) Resend
├── scripts/
│ └── build-og.mjs sharp: SVG → PNG build step
├── supabase-schema.sql `contact_messages` table + RLS + unread view
├── vercel.json Security headers, redirects, cache policy, short URLs
├── package.json Runtime: @supabase/supabase-js · Dev: sharp
└── .env.example
- Runtime — Static HTML, vanilla JS, CSS custom properties. No React, no Next.
- Host — Vercel static + one Node.js 24 serverless function (
/api/contact). - Database — Supabase Postgres with RLS; all inserts route through the service-role-wrapped API.
- Fonts — Plus Jakarta Sans (body) · Instrument Serif (display) · JetBrains Mono (labels).
- Motion — Native
IntersectionObserverscroll reveals. No animation library. - Build —
sharpresizes SVGs to PNGs during Vercel build. ~4s total. - Email (optional) — Resend for fan-out when a message comes in.
- HTML ~100 KB uncompressed → ~18 KB gzipped.
- Zero blocking JS. Fonts preconnected + swap.
- All animation uses
transformandopacityonly — no layout-triggering properties. backdrop-blurscoped to fixed elements only (nav, overlays). Never on scrolling content.- Lighthouse target: 100 / 100 / 100 / 100.
If you're forking this or adapting it for your own portfolio, follow this end-to-end.
You need:
- Node.js ≥ 20 — install from nodejs.org or via
brew install node - Git —
brew install giton macOS, or git-scm.com - A GitHub account — github.com
- A Vercel account — vercel.com (free Hobby plan is fine)
- A Supabase account — supabase.com (free tier is fine)
- Vercel CLI —
npm i -g vercel(for local dev + one-off deploys)
Check your versions:
node -v # should print v20.x or higher
git --version
vercel --versiongit clone https://github.com/tuttucodes/rahulbk.com.git
cd rahulbk.com
npm installnpm install pulls two packages: @supabase/supabase-js (used by the contact API)
and sharp (used by the build-time PNG generator).
Run the build once to verify it works:
npm run buildYou should see:
✓ public/og.png (1200×630)
✓ public/favicon-32.png (32×32)
✓ public/apple-touch-icon.png (180×180)
- Go to supabase.com/dashboard → New project.
- Pick a name, choose a region close to you, generate a strong database password.
- Wait ~2 minutes for the project to provision.
- Open SQL Editor → New query → paste the full contents of
supabase-schema.sql→ Run. - Verify: Table Editor now shows a
contact_messagestable with RLS enabled. - Go to Settings → API and copy:
- Project URL → you'll use this as
SUPABASE_URL service_rolekey (NOT anon) → you'll use this asSUPABASE_SERVICE_ROLE_KEY
- Project URL → you'll use this as
Security note. The
service_rolekey bypasses RLS by design. It only ever runs inside/api/contact.json Vercel's server — never shipped to the browser. The anon key is never used by this project because no client-side Supabase call exists.
cp .env.example .env.localOpen .env.local and fill in the values from Supabase:
SUPABASE_URL=https://YOUR_PROJECT_ID.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOi...your_real_key
# Optional — skip these for MVP
CONTACT_NOTIFY_EMAIL=rahulbabuk05@gmail.com
RESEND_API_KEY=re_...npx vercel devFirst run links your local folder to a Vercel project — let it create one (or link to an existing one). It then:
- Runs
npm run buildto generate PNGs. - Serves
public/athttp://localhost:3000. - Runs
api/contact.jsas a serverless function athttp://localhost:3000/api/contact.
Open the site, fill out the contact form, hit Send. Go back to Supabase →
Table Editor → contact_messages. The row should be there.
Troubleshooting:
vercel devrecursion error — remove any"dev"script inpackage.json.- Contact form returns 500 — your env vars aren't loaded. Confirm
.env.localexists and is populated. - Contact form returns 429 — rate limit: 6 submissions/hour/IP. Works as designed.
git init
git add .
git commit -m "initial commit"
git branch -M main
git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO.git
git push -u origin main- Vercel dashboard → Add New → Project → import your GitHub repo.
- Vercel detects
vercel.jsonautomatically. Leave framework preset as "Other". - Environment Variables — add the same two (or four) values from
.env.local. Apply to Production + Preview + Development. - Click Deploy. Takes ~30 seconds.
- Every future
git pushtomainauto-deploys.
vercel --prod- Project → Settings → Domains → add
yourdomain.comandwww.yourdomain.com. - Vercel shows you the DNS records needed.
Arecord on@→76.76.21.21— proxy OFF (Vercel issues the cert)CNAMEonwww→cname.vercel-dns.com— proxy OFF
Turn the Cloudflare proxy OFF (grey cloud) initially so Vercel can issue the SSL certificate directly. Once the cert is active, you can turn the proxy back on if you want Cloudflare caching in front.
Verify propagation at dnschecker.org. Usually 5 minutes, up to 24 hours worst case.
# Homepage
curl -sI https://yourdomain.com/ | head -4
# Build planner (cleanUrls)
curl -sI https://yourdomain.com/build-together | head -4
# API endpoint — expect 405 on GET (POST only)
curl -sI https://yourdomain.com/api/contact | head -4
# Short URL redirect
curl -sI https://yourdomain.com/calendar | grep locationAll should return 200 / 307 as appropriate.
After deploy, submit the site to every search + answer engine. Typical time to first indexing: 24–72 hours on Google, a week on Bing.
- Open search.google.com/search-console.
- Add property → Domain (enter
yourdomain.com— works for both apex and www). - Verify via DNS TXT record in Cloudflare (copy the
google-site-verification=…TXT value). - Once verified, go to Sitemaps → submit
https://yourdomain.com/sitemap.xml. - Under URL Inspection, paste your homepage and click Request Indexing. Repeat
for
/build-together.
Alternative verification via meta tag: if you don't want a TXT record, Search
Console also accepts an HTML meta tag. The template already includes the placeholder
at the top of public/index.html:
<meta name="google-site-verification" content="REPLACE_WITH_GOOGLE_TOKEN">Paste the token Google gives you, commit, deploy, then hit Verify.
- bing.com/webmasters → Add site.
- Easy path: Import from Google Search Console (one-click copy of verified properties).
- Or verify via the
msvalidate.01meta tag already placeholdered inindex.html. - Submit
sitemap.xml.
Bing also powers DuckDuckGo, Yahoo, and ChatGPT's web browsing — submitting here hits multiple surfaces at once.
- webmaster.yandex.com → add site → verify via the
yandex-verificationmeta tag already placeholdered.
This repo already implements IndexNow.
- Key:
8b9d62373913e61f567f866065f68f33bd98f7d9600627b4 - Key location:
public/8b9d62373913e61f567f866065f68f33bd98f7d9600627b4.txt— served athttps://rahulbk.com/8b9d62373913e61f567f866065f68f33bd98f7d9600627b4.txt - Safe to commit: yes. The key is public by protocol design — anyone visiting the key URL sees it. The only "risk" from an attacker is they can submit your own URLs to IndexNow using your key, which just re-indexes your own content.
- Submit script:
scripts/indexnow-submit.mjs
Every URL in public/sitemap.xml is POSTed as a single batch to
https://api.indexnow.org/indexnow. One request covers Bing, Yandex, Naver,
Seznam, and Yep simultaneously.
Push-to-index after a content change:
npm run submitOutput:
✓ Key file verified at https://rahulbk.com/903491c05a8964bdf6929cf46eb48ba1.txt
→ Submitting 4 URLs to IndexNow
· https://rahulbk.com/
· https://rahulbk.com/for-recruiters
· https://rahulbk.com/for-investors
· https://rahulbk.com/build-together
← 200 OK — crawlers queued
Override via env vars if you fork this repo:
INDEXNOW_HOST=yourdomain.com \
INDEXNOW_KEY=your-hex-key \
INDEXNOW_KEY_LOCATION=https://yourdomain.com/your-hex-key.txt \
npm run submitRotate the key by generating a new hex string (node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"), renaming the
.txt file to match, and updating the default in scripts/indexnow-submit.mjs.
The robots allowlist in public/robots.txt already explicitly
welcomes:
- GPTBot (ChatGPT / OpenAI)
- ClaudeBot (Anthropic)
- Google-Extended (Google's AI training crawler)
- PerplexityBot (Perplexity)
- Applebot-Extended (Apple Intelligence)
- CCBot (Common Crawl — feeds most open-source LLMs)
- cohere-ai, FacebookBot, Bytespider, ChatGPT-User
Structured data that helps AI answer engines surface the right content:
- Person schema → enables Google Knowledge Panel
- FAQPage schema → powers "People also ask" + direct answer extraction
- ProfessionalService schema → links into local/service search
- BreadcrumbList → breadcrumb display in SERPs
- WebSite with
SearchAction→ enables sitelinks search box
All are already embedded in <head> of index.html.
A human-readable summary for AI crawlers lives at
public/llms.txt — follows the emerging llmstxt.org
spec. Edit it whenever your key facts change.
- LinkedIn — add
yourdomain.comto your LinkedIn profile's Contact info. LinkedIn now shows AI-detected skills and links in searches. - GitHub profile README — pin this repo. Recruiters browsing your GitHub will see it first.
- Product Hunt / Hacker News / Indie Hackers — submit the AI build planner page
(
/build-together) as its own standalone tool. Backlinks from those properties have unusually high domain authority. - X / Twitter — drop the link with the OG preview attached. The
twitter:cardmeta is set tosummary_large_image, so it renders the full OG card.
- Vercel Analytics — one click in the Vercel dashboard. Free on Hobby plan.
- Google Search Console — check Performance weekly for query rankings.
- Ahrefs Webmaster Tools (free) — submit the site to track backlinks.
Expect 2–4 weeks for meaningful organic traffic. Search for "Rahul Babu" weekly — if the site isn't on page 1 after a month, add two or three press mentions or directory listings to juice the domain authority.
-- Unread inbox
select * from contact_messages_unread;
-- Mark handled
update contact_messages set handled = true where id = '<uuid>';
-- Purge obvious spam older than 30 days
delete from contact_messages
where created_at < now() - interval '30 days'
and message ~* '(crypto|bitcoin|seo expert|guest post)';Dual licensed.
- Code — MIT. Fork, learn, copy, commercialise. All the usual rights.
- Content, design, photography — CC BY-NC 4.0. You can share and adapt with attribution; you can't use it commercially without asking.
In plain English: take the code and build your own thing. Just don't lift the
written copy, the visual identity, or the photographs into something you sell.
For commercial use of content/design, email rahulbabuk05@gmail.com.
Rahul Babu — rahulbk.com · cal.com/rahulbabu · github.com/tuttucodes · rahulbabuk05@gmail.com
If you're building something interesting, say hi.