Personal portfolio of Harsh Raj - a fast, fully-featured React SPA with dark/light theme, live GitHub data, animated custom cursor, and a canvas contribution heatmap.
|
π SPA Navigation - zero full-page reloads |
ποΈ Scroll Reveal - IntersectionObserver fade-up animations |
| Layer | Choice |
|---|---|
| β‘ Build Tool | Vite 6 |
| βοΈ UI Framework | React 19 |
| π Routing | React Router v7 |
| πͺ Meta / SEO | react-helmet-async |
| π¨ Styling | Single global CSS (src/index.css) |
| π‘ Data | GitHub REST API + EmailJS |
| π Visit Counter | Upstash Redis via Vercel serverless |
| π Deployment | Vercel |
| URL | Page |
|---|---|
/ |
π Home - hero, about, journey preview, skills ticker, featured projects, GitHub heatmap |
/projects |
ποΈ All Projects - search, filter (frontend / fullstack / AI), sort |
/contact |
π¬ Contact - EmailJS form + social links |
/journey |
πΊοΈ Journey - experience & internship timeline |
/certificates |
π Certificates & Achievements |
* |
π 404 Not Found |
Portfolio/
βββ π index.html # SPA shell - single HTML entry point
βββ βοΈ vite.config.js
βββ π vercel.json # SPA rewrites + /api route
βββ π¦ package.json
βββ π Resume.pdf # Served from /Resume.pdf
β
βββ src/
βββ π main.jsx # BrowserRouter + HelmetProvider + ThemeProvider
βββ πΊοΈ App.jsx # Route definitions + AppShell wrapper
βββ π¨ index.css # Full design system + all component styles
β
βββ layouts/
β βββ ποΈ AppShell.jsx # Persistent shell - Nav, Footer, Cursor, Glows
β
βββ router/
β βββ π routes.jsx # Lazy imports for all pages
β βββ β¬οΈ ScrollToTop.jsx # Scrolls to top on every route change
β
βββ context/
β βββ π ThemeContext.jsx # Theme state + View Transition toggle
β
βββ hooks/
β βββ π‘ useProjects.js # GitHub projects fetch + cache + auto-refresh
β βββ ποΈ useGistContent.js # Gist CMS hook - experiences, internships, certs
β βββ ποΈ useReveal.js # IntersectionObserver scroll reveal
β βββ π useTheme.js # Re-export of useTheme from context
β
βββ services/
β βββ π githubProjects.js # Fetch + normalise portfolio repos
β βββ π₯ githubContributions.js # Fetch contribution heatmap data
β βββ π gistContent.js # Gist CMS - fetch + fallback for content
β
βββ components/
β βββ layout/
β β βββ π§ Navbar.jsx # Pill nav - tabs, GitHub icon, theme toggle
β β βββ π¦Ά Footer.jsx # Footer + visit counter
β β βββ π±οΈ CustomCursor.jsx # Dual-ring cursor (desktop only)
β β βββ π ScrollProgress.jsx # Top gradient progress bar
β β
β βββ home/
β βββ π¦Έ Hero.jsx # Hero section
β βββ π€ About.jsx # About section
β βββ πΊοΈ JourneyPreview.jsx # Journey + Certificates card links
β βββ π οΈ Skills.jsx # Ticker marquee tech stack
β βββ ποΈ FeaturedProjects.jsx # Top 2 projects from GitHub
β βββ π₯ GitHubHeatmap.jsx # Canvas contribution heatmap
β βββ π± BottomNav.jsx # Floating pill nav (home page only)
β
βββ pages/
βββ π Home.jsx
βββ ποΈ Projects.jsx # Full grid - search, filter, sort, pagination
βββ π¬ Contact.jsx # EmailJS form + toast
βββ πΊοΈ Journey.jsx # Timeline - data from Gist CMS
βββ π Certificates.jsx # Certificate cards - data from Gist CMS
βββ π NotFound.jsx # 404
Requirements: Node.js 18+
# Clone the repository
git clone https://github.com/RajHarsh03/Portfolio
cd Portfolio
# Install dependencies
npm install
# Start dev server
npm run devDev server runs at
http://localhost:5173
No
.envfile is needed to run or deploy this project.
| Variable | Where | Notes |
|---|---|---|
KV_REST_API_URL |
Vercel Dashboard β Storage | Auto-injected by Vercel when Upstash Redis is connected |
KV_REST_API_TOKEN |
Vercel Dashboard β Storage | Auto-injected by Vercel when Upstash Redis is connected |
π EmailJS keys are public client-side keys (hardcoded intentionally). Security is handled via domain whitelisting in the EmailJS dashboard. Ensure only
harshx.inis listed under Account β Security.
npm run build # β outputs production bundle to dist/
npm run preview # β preview the production build locally- Push to GitHub
- Import the repo on vercel.com
- Set the following options:
| Setting | Value |
|---|---|
| Framework Preset | Vite |
| Root Directory | / |
| Build Command | npm run build |
| Output Directory | dist |
- Connect Upstash Redis from Vercel Dashboard β Storage (auto-injects env vars)
- Deploy π
The
vercel.jsonhandles SPA routing - all URLs serveindex.htmland React Router takes over client-side.
To show a repo on the portfolio, add these topics to it on GitHub:
| Topic | Effect |
|---|---|
portfolio |
β Required - makes it appear on the portfolio |
rank-1, rank-2, β¦ |
π Controls display order (lower = higher priority) |
completed |
π’ Shows green "Completed" badge |
frontend / fullstack / ai |
π Sets the filter category |
personal / client / open-source / hackathon |
π·οΈ Sets the type pill label |
Tech icon topics (auto-mapped to devicons): react, nodejs, python, typescript, mongodb, tailwindcss, and more.
Content for the Journey and Certificates pages is managed via a GitHub Gist β no code push needed to add or edit cards.
The app fetches a single JSON file from a private Gist at runtime. If the Gist is unreachable, it automatically falls back to hardcoded data so the site never breaks.
Gist URL β fetched on every page visit (cached 5 min)
β fails?
Fallback data in gistContent.js (always shown instantly)
- Go to your Gist: https://gist.github.com/
- Click Add Gist
- Add the JSON structure for (
experiences,internships, orcertificates) - Click Create secret gist
- Click Raw after creating the gist
- Copy the raw url after clicking Raw
- Replace the copied url in the src/services/gistContent.js with the Replace with your URL - changes are live within 5 minutes, no deploy needed
- For any changes just addd/modify the present gist, dont create a new one
Experience / Internship card:
{
"label": "INTERNSHIP",
"type": "INTERNSHIP",
"typeColor": "badge-purple",
"company": "Company Name",
"role": "Your Role Title",
"date": "JAN 2025 - PRESENT",
"bullets": [
"First bullet point about what you did.",
"Second bullet point with an achievement."
]
}Certificate card:
{
"label": "COURSE",
"type": "CERTIFICATE",
"typeColor": "badge-teal",
"title": "Certificate Title",
"issuer": "ISSUING BODY",
"date": "JAN 2025",
"desc": "Short description of what this certificate covers.",
"link": "https://drive.google.com/..."
}typeColor options: badge-blue | badge-purple | badge-teal
To update the fallback (shown before Gist loads), also edit FALLBACK_DATA in src/services/gistContent.js and push.
Uses a Vercel serverless function at /api/visits:
POST /api/visits β Records unique daily visit (hashed IP + UA + date), returns total
GET /api/visits β Returns total count without incrementing
Backed by Upstash Redis (Vercel KV). In local dev it reads from the live production endpoint so the counter is always visible.
MIT License - feel free to use as a reference or template.
A credit link back to harshx.in is appreciated but not required.
Made by Harsh Raj