Personal portfolio and resume website built by George Khananaev.
- Next.js 16 App Router with server and client components, Turbopack build
- React 19 with modern patterns (no memo, no FC, no forwardRef)
- TypeScript 5.9 with strict mode
- Tailwind CSS 4 CSS-first configuration
- MongoDB 7 for the portfolio blog, via the native
mongodbdriver (no ORM) - unified / remark / rehype Markdown → HTML pipeline with
rehype-sanitizeandrehype-prism-plussyntax highlighting - Sharp for build-time OG banner, icon, and favicon generation
- jspdf for on-demand
/api/resumePDF generation - Headless UI 2 and Heroicons 2
- ESLint 9 flat config (native
eslint-config-next) with Prettier
- MongoDB-backed portfolio blog at
/portfoliowith per-post dynamic routes, per-post dynamic OG images, RSS feed, tag archives, and LLM-friendly/llms.txt— the whole catalog is seeded fromscripts/seed-posts.mjs - Editorial dark redesign across Hero, About, Resume, GithubStats, Services, FavoriteTech, Testimonials, and Footer — shared indigo palette, grid-mesh backdrops, and monospace category labels
- Modern Header with
usePathnameactive-page state, GK avatar, pill-style indicators, and a mobile slide-in drawer - Dedicated
/contactand/work-with-mepages replacing the old single-page layout, with proper ContactPage / ProfessionalService JSON-LD - GitHub Stats Section with live repo count, stars, followers, achievements, and top languages (fetched server-side, cached 1hr)
- Dynamic PDF Resume at
/api/resumeusing jsPDF — includes the editorial grid mesh, reads content from the sameaboutData.descriptionas the home page - Health check endpoint at
/api/health— runs a real MongoDB ping + env var audit, returns public-safe JSON, HTTP 503 on degradation. The Footer shows a live green/red indicator next to the version badge - Contact Form with reCAPTCHA v3 (score ≥ 0.5 + action match), server-side IP rate limiting, WhatsApp phone input with bundled country flag icons, EmailJS dispatch
- Strict Content Security Policy + all the modern security headers (HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)
- SEO via Next.js Metadata API, per-page JSON-LD (Person, WebSite, WebPage, Article, BreadcrumbList, ProfessionalService, ContactPage), sitemap, robots, OG + Twitter cards
- ISR (Incremental Static Regeneration) with 1-hour revalidation on blog pages and GitHub data
src/
├── app/ # Next.js App Router
│ ├── layout.tsx # Root layout, metadata, icons
│ ├── page.tsx # Home page with JSON-LD Person graph
│ ├── globals.css # Tailwind + custom animations (@theme + @keyframes)
│ ├── contact/ # /contact page
│ ├── work-with-me/ # /work-with-me page
│ ├── portfolio/ # Portfolio list, [slug], tag/[tag], rss.xml, per-post OG image
│ ├── llms.txt/ # LLM-friendly catalog route
│ └── api/
│ ├── contact/ # Rate-limited contact form endpoint
│ ├── health/ # Health check endpoint (Mongo ping + env audit)
│ ├── posts/ # Internal-secret-gated post endpoint
│ └── resume/ # Dynamic PDF resume generator
├── components/
│ ├── Layout/ # PageHero, PageBreadcrumbs, Section
│ ├── Sections/ # Hero, About, Resume/, Footer, Header, etc.
│ ├── Portfolio/ # PostCard, PostRow, PostHeader, PostBody, etc.
│ ├── Icon/ # SVG icon components
│ └── HealthIndicator.tsx # Live /api/health status dot in Footer
├── data/
│ ├── data.tsx # Site content (hero, about, experience, skills, etc.)
│ └── dataDef.ts # TypeScript interfaces
├── lib/
│ ├── db.ts # Cached MongoDB client
│ ├── posts.ts # Post queries + ISR helpers
│ ├── markdown.ts # unified/remark/rehype pipeline
│ ├── github-stars.ts # Live star count fetcher
│ ├── breadcrumb.ts # Schema.org BreadcrumbList helper
│ └── version.ts # Compile-time APP_VERSION from package.json
├── types/ # Shared TypeScript types (Post, etc.)
└── images/ # Static assets and SVGs
- Node.js 20+
- npm
- A free MongoDB Atlas account (M0 tier — 500 MB, no credit card, plenty for this site)
The portfolio blog, sitemap, RSS feed, and /llms.txt all read from a MongoDB collection. Without a database the site won't boot — src/lib/db.ts throws if MONGODB_URI isn't set.
- Sign up at https://www.mongodb.com/ and create a new project
- Deploy a free-tier M0 cluster (500 MB storage, no cost, no card required)
- Under Database Access, create a user with read/write permission on the cluster
- Under Network Access, whitelist your IP — or
0.0.0.0/0if you're deploying to Netlify / Vercel and can't pin a single egress IP - Click Connect → Drivers → copy the SRV connection string (looks like
mongodb+srv://<user>:<password>@cluster.mongodb.net/...) - Paste it into your
.envasMONGODB_URI, replacing<user>and<password>with your real credentials - Set
MONGODB_DBto whatever name you want for the database (e.g.your_name_portfolio) — the driver creates it on first write
# Clone the repository
git clone https://github.com/georgekhananaev/resume-website.git
cd resume-website
# Copy environment variables and fill them in
cp .env.example .env
$EDITOR .env
# Install dependencies
npm installRun the seed script once to populate MongoDB with the starter portfolio posts. Re-run any time you edit scripts/seed-posts.mjs — the script is idempotent (upserts by slug):
node --env-file=.env scripts/seed-posts.mjsnpm run devOpen http://localhost:3000. Visit http://localhost:3000/api/health to verify MongoDB is reachable — you should see {"status":"ok",...}.
Copy .env.example to .env and fill in your values:
| Variable | Server/Client | Description |
|---|---|---|
MONGODB_URI |
Server | MongoDB Atlas SRV connection string — free M0 tier gives 500 MB, no credit card needed |
MONGODB_DB |
Server | Database name (e.g. your_name_portfolio). Falls back to george_khananaev_portfolio if unset |
EMAILJS_SERVICE_ID |
Server | EmailJS service ID |
EMAILJS_TEMPLATE_ID |
Server | EmailJS template ID |
EMAILJS_PUBLIC_KEY |
Server | EmailJS public key |
EMAILJS_PRIVATE_KEY |
Server | EmailJS private/access key |
EMAILJS_TO_EMAIL |
Server | Recipient email for contact form |
NEXT_PUBLIC_RECAPTCHA_SITE_KEY |
Client | Google reCAPTCHA v3 site key |
RECAPTCHA_SECRET_KEY |
Server | Google reCAPTCHA v3 secret key |
NEXT_PUBLIC_DISABLE_RECAPTCHA |
Client | Set true to disable reCAPTCHA in dev (ignored in production) |
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION |
Client | Google Search Console verification |
GITHUB_USERNAME |
Server | GitHub username for stats section |
GITHUB_PRIVATE_REPOS |
Server | Number of private repos (added to public count) |
GITHUB_TOKEN |
Server | GitHub fine-grained token (for rate limits) |
BIRTH_DATE |
Server | Birth date for dynamic age calculation (YYYY-MM-DD) |
SITE_URL |
Build | Canonical site URL used in sitemap, OG tags, and JSON-LD |
All site content lives in src/data/data.tsx. Update text, images, links, and the site reflects changes on save. Portfolio SVG illustrations are in src/images/portfolio/. Blog posts live in MongoDB — edit and re-run scripts/seed-posts.mjs to update them.
OG banner, PWA icons, apple-touch-icon, and the GK favicon are all regenerated from a single source photo via:
node scripts/generate-images.mjsThe script reads public/george_khananaev_ws.png and writes public/og-image.png, icon-512.png, icon-192.png, apple-touch-icon.png, and favicon.ico. Brand colors are constants at the top of the script — edit and re-run to rebrand.
npm run build
npm start| Script | Description |
|---|---|
npm run dev |
Start dev server with Turbopack |
npm run build |
Production build |
npm start |
Start production server |
npm run lint |
Run ESLint (flat config, native eslint-config-next) |
npm run typecheck |
Run tsc --noEmit for type-only check |
npm run clean |
Remove .next build cache |
Custom EmailJS email templates are in docs/:
docs/emailjs-template.html— notification sent to you when someone submits the contact formdocs/emailjs-auto-reply-template.html— auto-reply confirmation sent to the sender
Copy the HTML into your EmailJS template editor. Templates use {{from_name}}, {{from_email}}, {{from_phone}}, {{message}}, {{title}}, and device info variables.
See CHANGELOG.md for a detailed list of changes.
Initial design based on a template by Tim Baker. Fully refactored, modernized, and maintained by George Khananaev — including migration to Next.js 16 App Router, React 19, Tailwind CSS 4, server components architecture, and all current functionality.
MIT — see LICENSE for details.
