A minimal, editorial portfolio built with Next.js 14, Tailwind, and TypeScript.
Real-time Discord presence, Spotify now-playing, GitHub heatmap, MDX blog with syntax highlighting, dynamic OG images, and a chat-style contact form.
One-click deploy on Vercel:
- Discord presence via Lanyard with real status dot + current activity
- Spotify now playing with album art
- GitHub contributions heatmap + live star count badge in footer
- MDX blog at /writing — auto reading time, per-post dynamic OG images, share menu (X, Facebook, Discord, copy link), related posts, syntax highlighted code blocks with copy button
- Chat-style contact form — sequential prompts, posts to Discord webhook
- Cal.com booking modal embedded in homepage footer
- Sound feedback via Cuelume — hover ticks, press sounds, success chimes on every interaction
- Dark/light theme with instant swap
- SEO-optimized — full metadata, JSON-LD Person + WebSite + BlogPosting schema, robots.txt, dynamic sitemap
- AI crawler friendly — GPTBot, ClaudeBot, Gemini, Perplexity explicitly allowed
- Dark mode extension blocker — locks out Dark Reader, Night Eye, etc. so your theme stays yours
- Editorial typography (Inter + Geist Mono), squircle avatars, no unnecessary animations, instant client-side navigation
- Framework: Next.js 14.2 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS
- Fonts: Inter (body) + Geist Mono (meta/labels)
- Content: MDX via next-mdx-remote, gray-matter, reading-time
- Syntax highlighting: rehype-pretty-code + shiki (github-dark-dimmed theme)
- OG images: @vercel/og with bundled Inter fonts and per-post cards
- Icons: lucide-react, react-icons
- Data: SWR + async server components
- Analytics: Vercel Analytics
- Booking: @calcom/embed-react
- Sound feedback: cuelume
git clone https://github.com/shahriaravi/cooked-folio.git
cd cooked-folio
npm install
npm run dev
Open http://localhost:3000.
Copy .env.example to .env.local:
cp .env.example .env.local
| Variable | Description |
|---|---|
NEXT_PUBLIC_URL |
Your live site URL |
DISCORD_WEBHOOK_URL |
Discord webhook for contact messages |
NEXT_PUBLIC_DISCORD_USER_ID |
Your Discord User ID |
SPOTIFY_CLIENT_ID |
Spotify app Client ID |
SPOTIFY_CLIENT_SECRET |
Spotify app Client Secret |
SPOTIFY_REFRESH_TOKEN |
Spotify refresh token |
GITHUB_USERNAME |
Your GitHub username |
GITHUB_TOKEN |
GitHub PAT with read:user |
Content → src/lib/config.ts
Socials, stack, experience, education, projects, external links.
SEO → src/lib/site-config.ts
Metadata, keywords, JSON-LD schema, OpenGraph, Twitter cards. Every page defaults to Shahriar Avi as title unless overridden per-page.
Blog posts → src/content/writing/*.mdx
Filename becomes slug. Frontmatter is minimal:
---
title: "Post Title"
date: "2026-01-15"
---
Reading time, word count, slug, and per-post OG image all auto-generated.
OG images → src/app/writing/[slug]/opengraph-image.tsx
Dynamic per-post preview cards using your background at public/images/og-bg.png, avatar, post title, and reading time. Fonts bundled at public/fonts/Inter-Regular.ttf + Inter-Bold.ttf.
src/
├─ app/
│ ├─ api/ # contact, discord, spotify, github routes
│ ├─ writing/
│ │ ├─ page.tsx # blog list
│ │ └─ [slug]/
│ │ ├─ page.tsx # dynamic post page (SSG)
│ │ └─ opengraph-image.tsx # per-post OG image generator
│ ├─ contact/ # chat-style form
│ ├─ layout.tsx # root: Providers, NavbarWrapper, SiteFooterWrapper, JSON-LD, fonts
│ ├─ sitemap.ts # dynamic sitemap
│ └─ page.tsx # homepage
├─ components/
│ ├─ common/ # Container, ThemeToggle, CodeBlock, CopyButton, CustomScrollArea
│ ├─ layout/ # Hero, Navbar, NavbarWrapper, HomeFooter, SiteFooter, SiteFooterWrapper, Providers
│ ├─ sections/ # Experience, Education, Projects, Stack
│ ├─ integrations/ # Discord, Spotify, GitHub cards
│ ├─ contact/ # ContactForm
│ ├─ writing/ # WritingList, MdxComponents, ShareMenu, MorePosts, ArticleJsonLd
│ └─ ui/ # TimeDisplay, Folder, LogoLoop, PixelBlast
├─ content/writing/ # MDX blog posts
├─ hooks/ # useDiscordPresence
└─ lib/ # config, site-config, writing, utils
public/
├─ fonts/ # Inter-Regular.ttf, Inter-Bold.ttf (for OG images)
├─ images/ # og-bg.png (OG background)
└─ avatar/ # avatar.png, luffy.png, avatar-fill.png
- Content width: 44rem (704px) centered column
- Fonts: Inter body, Geist Mono for meta/labels/dates
- Section overline:
text-[11px] font-mono uppercase tracking-[0.14em] - Body copy:
16px / 24px / 0.2px - Headings:
22–28px semibold, tight letter-spacing - Avatars: squircle with fixed px radius
- Cards:
rounded-2xlwith subtle border + hover tint - Interactions: instant CSS transitions, no page fade animations, no unnecessary pop-ins
- Sticky footer:
flex min-h-[100dvh] flex-collayout keeps footer at bottom on short pages - Sound design: Cuelume for tactile audio feedback on hover, press, success, error
Add a new blog post by creating a .mdx file:
src/content/writing/my-new-post.mdx
Filename becomes the URL slug (/writing/my-new-post). The list page, related posts, sitemap, and per-post OG image all update automatically on next build.
Code blocks support syntax highlighting when you specify a language after the opening backticks (tsx, ts, css, bash, powershell, python, and 100+ more via shiki). Every code block gets an always-visible copy button.
The site actively blocks browser extensions like Dark Reader from overriding its theme, since it already ships proper dark mode. Handled via:
<meta name="darkreader-lock" />in the headdarkreader-ignoreclass +data-darkreader-ignoreattribute on<html>- CSS reset for any
[data-darkreader-inline-*]attributes color-scheme: only light/darklocks
Full explanation in /writing/block-dark-mode-extensions.
- Fork
git checkout -b feat/thinggit commit -m "feat: add thing"git push origin feat/thing- Open PR
MIT © 2026 Shahriar Avi