Portfolio site for Saad Nasir — .NET backend & microservices engineer. Static today, structured so dynamic content can be added without reworking the UI.
| Concern | Choice |
|---|---|
| Framework | Next.js 16 (App Router), React 19 |
| Language | TypeScript |
| Styling | Tailwind CSS v4 (CSS-first config in globals.css) |
| Animation | motion (scroll reveals) + CSS keyframes (hero) |
| Analytics | @vercel/analytics, @vercel/speed-insights |
| Hosting | Vercel (all routes prerender to static HTML) |
npm install
npm run dev # http://localhost:3000
npm run build # production build — also runs typecheck
npm run lint
npm start # serve the production buildAll copy lives in typed modules under src/content/ — no CMS required. Edit these
and the pages update:
| File | Controls |
|---|---|
src/content/site.ts |
Name, role, tagline, email, location, socials, CV link |
src/content/capabilities.ts |
The "What I help with" cards |
src/content/work.ts |
Case studies — home-page cards + /work/[slug] pages |
src/content/experience.ts |
Work history and education |
src/content/skills.ts |
The grouped skills list in the About section |
Append an entry to the work array in src/content/work.ts. Set featured: true
to surface it as a card on the home page (all entries get a /work/<slug> page
and an OG image automatically). Structure the copy as problem → approach → outcome;
only use metrics that can be stood behind.
Drop a PDF into public/ (e.g. public/saad-nasir-cv.pdf) and set
cvUrl: "/saad-nasir-cv.pdf" in src/content/site.ts. The link is hidden while
cvUrl is null. Note: the current résumé references a different GitHub handle
and email than this site — reconcile before publishing it.
- Per-route metadata + title template in
src/app/layout.tsxand eachpage.tsx src/app/sitemap.tsandsrc/app/robots.ts(generated at build)- JSON-LD:
Person+ per-case-studyCreativeWork - OG images rendered with
next/og:src/app/opengraph-image.tsxandsrc/app/work/[slug]/opengraph-image.tsx - Canonical URL is derived from
site.url— keep it accurate
- Push this repo to GitHub.
- In Vercel: New Project → import the repo → framework auto-detects as Next.js → Deploy. No env vars needed.
- Analytics: in the Vercel project, enable Web Analytics and Speed Insights (Analytics tab). The scripts 404 locally — that's expected; they only serve on Vercel.
- Custom domain: Project → Settings → Domains → add
saad.works. At thesaad.worksregistrar, set the records Vercel shows — typically anArecord for the apex to76.76.21.21, or follow Vercel's nameserver option. Addwww.saad.worksas a redirect to the apex if desired. - After DNS resolves, confirm
https://saad.works/sitemap.xmlandhttps://saad.works/robots.txtrespond, then submit the sitemap in Google Search Console.
The content modules are the seam. To move any of them to a database or CMS,
replace the file's exported constant with an async function of the same return
type and await it where it's consumed (the pages are already async Server
Components). A contact form would be a Route Handler at src/app/api/contact/route.ts
plus an email provider (e.g. Resend) — the Contact section currently uses a
mailto: link and a copy-to-clipboard button.
Light/dark via a .dark class on <html>. A tiny inline script in the document
head sets it from localStorage (falling back to the OS preference) before paint
to avoid a flash; ThemeToggle flips and persists it. Palette tokens are defined
once in src/app/globals.css.