I recently polished and open-sourced my personal portfolio: a Next.js site with JSON-driven content, static export, and GitHub Actions deploying to GitHub Pages—no server, no API routes at runtime.
If you’re comparing stacks for a portfolio or fighting subpath deploys (username.github.io/repo/), this might save you a few rabbit holes.
Live site & repo
What it is
A single-page style home with hero, featured work, about, technical stack, and experience, plus extra routes for projects (GitHub API at build time) and articles (Medium RSS via rss2json, also at build time). Most copy and structure live under content/ as JSON, so updates don’t always mean digging through JSX.
Stack in practice:
- Next.js (Pages Router), React, Sass
- Font Awesome free icons (with a small util to map old Pro-style prefixes to free solid)
- Framer Motion for motion where it helps
- Jest + Testing Library for a few unit tests
Why static export + GitHub Pages
I wanted:
- Free hosting tied to the repo
- Predictable URLs for a project site under a subpath
- No need to run Node in production—just static files after
next build
next.config.js uses output: 'export' so the app becomes plain HTML/JS/CSS in out/. GitHub Pages serves that output from the workflow in .github/workflows/deploy-github-pages.yml.
The annoying part of project pages is the /portfolio/ base path: assets, favicons, manifests, and client-side public URLs must stay consistent. The repo uses basePath / assetPrefix derived from the GitHub repo name in CI, plus a small publicPath() helper so links to public/ files work both locally (/) and on Pages (/portfolio/).
Content model (quick mental model)
Rough map (details in the README):
| Area | Where it lives |
|---|---|
| Settings / GitHub username for APIs | content/_settings.json |
| Hero & page colors |
content/index/hero.json, content/index/_colors.json
|
| Featured projects | content/projects/featured.json |
| Technical section | content/index/technical.json |
| Experience | content/index/experience.json |
| Footer | content/footer.json |
That split kept the site maintainable as I iterated on copy and featured work.
Things I’d tell my past self
-
Static export means no
pages/apiin the classic sense—anything dynamic either runs at build time (getStaticProps+fetch) or moves to an external service. -
images.unoptimizedis the usual tradeoff for static hosting without an image CDN. -
Subpath deploys are solvable but tedious: manifest icons, document head, and any hardcoded
/foopaths will bite you until everything goes through your path helper or Next’sbasePath. - Favicons: SVG favicons are great, but external images inside SVG favicons are unreliable in Chromium; inlining the bitmap (e.g. data URI) fixed “blank tab icon” for me.
Try it locally
bash
git clone https://github.com/MehtabRiaz/portfolio.git
cd portfolio
npm install
npm run dev
Top comments (0)