Production-ready Next.js port of the portfolio/v1-sidebar.html handoff design, built with the App Router, Tailwind, Framer Motion, and Sanity. Ships with live GitHub + LeetCode data: a combined activity graph (heatmap rows) and recent signals from public APIs (fetched on each home page request).
- Framework: Next.js 14 (App Router, TypeScript)
- Styling: Tailwind CSS + raw CSS variables (design tokens from
tokens.css) - Animations: Framer Motion (scroll reveal, card hover, theme toggle)
- CMS: Sanity (schemas + embedded Studio at
/studio)
app/
layout.tsx # Root layout, fonts, theme provider
page.tsx # Home page — composes all sections
globals.css # Design tokens + base + component classes
studio/[[...tool]]/ # Embedded Sanity Studio
components/
Sidebar.tsx # Sticky left rail with scroll-spy
sections/ # Hero, About, Now, Experience, Skills, Projects, Contact, Footer
ui/ # Reveal, Chip, SectionHead, ThemeToggle, icons
ThemeProvider.tsx # Light/dark context (localStorage-persisted)
lib/
content.ts # Sanity fetchers w/ graceful fallbacks
handles.ts # Resolve GitHub / LeetCode logins from social URLs
activity.ts # Activity graph + live stats merge for the home page
github.ts # GitHub REST + GraphQL (contributions, public events)
leetcode.ts # LeetCode GraphQL (stats, calendar, recent solves)
cn.ts
sanity/
client.ts # Published read client
env.ts
queries.ts # GROQ
schemas/ # profile, project, experience, skillCategory, social
fallback.ts # Static design content (used when Sanity isn't set up)
types.ts
sanity.config.ts # Studio config
tailwind.config.ts # Token mappings
cp .env.example .env.local
# Fill in Sanity + GitHub + LeetCode vars (optional; the site still renders without them)
# Add NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY to enable the contact form
npm install
npm run dev
# http://localhost:3000 — portfolio
# http://localhost:3000/studio — Sanity StudioEverything on the home page is pre-populated with the handoff design content via sanity/fallback.ts, so you can preview the full page immediately without connecting Sanity.
- Create a project at sanity.io/manage.
- Set the following in
.env.local:NEXT_PUBLIC_SANITY_PROJECT_ID=<your id> NEXT_PUBLIC_SANITY_DATASET=production - Run
npm run devand open/studio. Create one document of each type:- Profile (singleton — name, headline, facts, etc.)
- Experience (one per role — ordered via
order) - Skill Category (one per group)
- Project (title, description, viz style, tech chips)
- Social link (email, GitHub, LinkedIn, LeetCode)
- Publish each document. Sanity reads use
revalidate: 0so content updates on the next request after you publish.
lib/activity.ts (getLiveStats) loads social handles from Sanity (or fallbacks), then calls GitHub and LeetCode on the server. Static numbers in sanity/fallback.ts only fill gaps when an API call fails.
- GitHub — repo count, merged PRs, contribution total from the calendar window, top languages, contribution calendar (GraphQL when
GITHUB_TOKENis set; otherwise public events), and public events for the signals card. Login from the GitHub social URL, elseGITHUB_USERNAME. - LeetCode — contest rating, ranking, solve counts, submission calendar (heatmap weeks aligned to GitHub), and recent accepted submissions for signals. Username from the LeetCode social URL (
/u/usernamerecommended), elseLEETCODE_USERNAME.
GitHub / LeetCode fetch calls use Next.js revalidate hints where applicable so repeated loads can be cached briefly at the data layer.
- Push this repo to GitHub.
- Import into Vercel.
- Add the env vars from
.env.example(addGITHUB_TOKENas a secret if you use it). - First deploy.
- Configure the Vercel project → Settings → Domains if using a custom domain (e.g.
alanansari.dev).
- Tokens from
tokens.cssare ported verbatim intoapp/globals.css(OKLCH colors, easing curves, radius, shadow stack). - Section structure and spacing match the handoff (
120pxvertical padding on desktop,80px 80pxhorizontal). - The sidebar uses an IntersectionObserver scroll-spy, mirroring the prototype.
- Each project card has its own visualization (
buzrrbars,benefichart,jsgamezgrid,samriddhiconic) implemented in CSS. - Theme toggle persists via
localStorage(keyportfolio-theme) and hydrates before paint via an inline script in<head>— no dark-mode flash.
| Command | Description |
|---|---|
npm run dev |
Start local dev server (Next + Studio) |
npm run build |
Production build |
npm run start |
Start built server |
npm run lint |
ESLint (Next config) |
npm run typecheck |
Type-check without emit |