Ein's Digital Laboratory. Not a portfolio. A laboratory. Experiments, prototypes, and open-source artifacts β forged, tested, and refined in public.
- Philosophy
- Live Demo
- Features
- Tech Stack
- Project Structure
- Getting Started
- Content Authoring β MDX
- Routes & App Router
- Design System
- SEO & Performance
- Scripts
- Deployment
- Contributing
- License & Author
EINCODE is built as a digital workshop, not a static showcase:
- Forge in public β every experiment ships with source, live demo, and lab notes.
- Lab over gallery β WIP is first-class. Progress bars, status indicators, and field notes tell the story of how it was built.
- Content as code β blog posts are
MDXfiles versioned incontent/(content/building-linux-distro-from-scratch.mdx:1), rendered withnext-mdx-remote+shiki+rehype. - Modern by default β Next.js 16 App Router, React 19 Server Components, Tailwind v4 CSS-first config,
next/fontoptimized fonts.
If you want a template for a fast, SEO-perfect, MDX-powered personal site that still feels like a hacker lab β this is it.
| Environment | URL |
|---|---|
| Production | https://eindev.ir |
| Alt Domain | https://eincode.dev (see .env.example:2) |
Preview the hero's typewriter, projects grid, lab notes, and workbench directly on the homepage β app/page.tsx:10 composes Header, HeroSection, ProjectsGrid, LabNotes, Workbench, Footer.
| Area | What you get |
|---|---|
| Hero Lab | Typewriter role rotation ("building interfaces", "crafting code"), ASCII terminal, floating version badge β components/hero-section.tsx:6 |
| Artifacts Grid | 8 open-source projects with shipped / in-progress / archived filters, stars/forks, glassmorphism & hover-lift β components/projects-grid.tsx:7 |
| Workbench | Live WIP terminal with progress bars (60β90%), ~/ehsanghaffar/active header β components/workbench.tsx:4 |
| Lab Notes | 4 field notes with category pills & gradient hovers β components/lab-notes.tsx:7 |
| Blog Engine | File-based MDX in content/ (8 posts), gray-matter frontmatter, GFM, autolink headings, pretty-code β lib/mdx-utils.ts:8 |
| Theming | Dark/light/system with next-themes, ThemeProvider (app/layout.tsx:100), ThemeToggle + ThemeChanger, OKLCH tokens, globals.css:8 |
| MDX Pipeline | next-mdx-remote + rehype-slug + rehype-autolink-headings + rehype-pretty-code (shiki) + remark-gfm β components/mdx-remote.tsx:1 |
| SEO Mastery | metadata (app/layout.tsx:25), JSON-LD (lib/structured-data.ts:32), sitemap.ts:4 + robots.ts:3, OG image public/og-image.png |
| A11y + Perf | next/font (Geist, Geist_Mono, Space_Grotesk), AVIF/WebP images (next.config.mjs:5), reduced-motion, touch targets 44px β app/globals.css:640 |
| UI Kit | shadcn/ui on Radix primitives β 20+ components in components/ui/ (accordion, dialog, dropdown-menu, etc.) |
| Analytics | Vercel Analytics injected in root layout β app/layout.tsx:103 |
| Tech | Version | Purpose | Config |
|---|---|---|---|
| Next.js | 16.1.0 |
App Router, Turbopack, SSR/SSG, Metadata API | next.config.mjs:1 |
| React | 19.2.3 |
Server Components, use client islands |
package.json:64 |
| TypeScript | 5.9.3 |
Strict types, path alias @/* |
tsconfig.json:25 |
| Tailwind CSS | 4.1.9 |
CSS-first @import "tailwindcss", OKLCH, @plugin typography |
app/globals.css:1 |
| Tech | Details |
|---|---|
| Radix UI | 16 primitives β accordion, dialog, popover, tabs, toast, etc. β package.json:24 |
| shadcn/ui | components.json + components/ui/ + lib/utils.ts:1 (cn = clsx + tailwind-merge) |
| Lucide React | 0.454.0 icons throughout |
| next-themes | 0.4.6 class-based dark mode |
| tw-animate-css | 1.3.3 animation utilities |
| Tech | Usage |
|---|---|
next-mdx-remote 6.0.0 |
Serialize & hydrate MDX |
gray-matter 4.0.3 |
Frontmatter parsing β lib/mdx-utils.ts:3 |
remark-gfm 4.0.1 |
GitHub Flavored Markdown |
| rehype-slug / autolink-headings / pretty-code | Anchors + Shiki highlighting |
shiki 3.22.0 |
Syntax highlighting |
@tailwindcss/typography 0.5.19 |
Prose styling .prose β app/globals.css:163 |
| Tech | Version |
|---|---|
| pnpm | >=9 recommended (lockfile pnpm-lock.yaml) |
| ESLint | eslint . β package.json:18 |
| Vercel Analytics | latest |
zod 3.25, react-hook-form 7.60, recharts 2.15, date-fns 4.1, embla-carousel... |
Extra UI deps pre-wired |
eincode/
βββ app/ # Next.js 16 App Router β app/layout.tsx:92
β βββ (public)/ # Route group (shared layout)
β β βββ blog/
β β β βββ [postSlug]/page.tsx # Dynamic MDX post β lib/mdx-utils.ts:34
β β β βββ page.tsx # Blog index
β β βββ projects/page.tsx
β β βββ workbench/page.tsx
β β βββ notes/page.tsx
β β βββ introduction/page.tsx
β β βββ layout.tsx
β βββ page.tsx # Homepage β composes 6 sections
β βββ layout.tsx # Root layout, fonts, metadata, ThemeProvider
β βββ globals.css # Tailwind v4 + OKLCH tokens + animations
β βββ sitemap.ts # Dynamic sitemap from MDX β app/sitemap.ts:4
β βββ robots.ts # Robots.txt β app/robots.ts:3
β βββ error.tsx / not-found.tsx
β βββ favicon.ico
βββ components/
β βββ hero-section.tsx # Typewriter + ASCII terminal
β βββ projects-grid.tsx # Filterable artifacts grid
β βββ workbench.tsx # Terminal-style WIP list
β βββ lab-notes.tsx # Field notes grid
β βββ header.tsx / footer.tsx
β βββ cursor-glow.tsx
β βββ mdx-remote.tsx / mdx-components.tsx
β βββ theme-provider.tsx / theme-toggle.tsx / theme-changer.tsx
β βββ ui/ # 20+ shadcn/ui components
βββ content/ # MDX blog source (8 posts)
β βββ building-linux-distro-from-scratch.mdx
β βββ mcp-protocol-llm-applications.mdx
β βββ nextjs-16-tailwind-v4-migration.mdx
β βββ ... (5 more)
βββ lib/
β βββ mdx-utils.ts # getAllMDXPosts(), getMDXPostBySlug()
β βββ blog-data.tsx # Legacy in-memory posts + helpers
β βββ structured-data.ts # JSON-LD: BlogPosting, WebSite, Person
β βββ themes.ts / utils.ts
βββ types/mdx.ts # BlogPostFrontmatter, BlogPost
βββ public/
β βββ og-image.png / og-images/
β βββ icon.svg / apple-icon.png
β βββ site.webmanifest
βββ docs/ # architecture.md, performance.md, etc.
βββ next.config.mjs # AVIF/WebP, deviceSizes
βββ components.json # shadcn config
βββ tsconfig.json # @/* alias
βββ package.json
- Node.js
>=18(20+ recommended) - pnpm β
npm i -g pnpmor see pnpm.io - Git
git clone https://github.com/ehsanghaffar/eincode.git
cd eincode
pnpm installcp .env.example .env
# edit .env| Variable | Required | Default | Description |
|---|---|---|---|
NEXT_PUBLIC_SITE_URL |
No | https://eindev.ir |
Canonical URL for metadata/sitemap β app/layout.tsx:26 |
NEXT_PUBLIC_GITHUB_URL |
No | https://github.com/ehsanghaffar |
Social link |
NEXT_PUBLIC_TWITTER_URL |
No | β | Social link |
NEXT_PUBLIC_LINKEDIN_URL |
No | β | Social link |
No database, no API keys. The site is fully static + MDX. Analytics is zero-config on Vercel.
pnpm dev
# β http://localhost:3000Hot reload is near-instant with Next.js 16 Turbopack.
pnpm build
pnpm start
# β http://localhost:3000 (optimized)All posts live in content/*.mdx and are read at build time via lib/mdx-utils.ts:8.
---
title: "Building a Linux Distro from Scratch"
excerpt: "A comprehensive guide to compiling the kernel..."
date: "2025-11-15" # display date
dateISO: "2025-11-15" # used for sorting β lib/mdx-utils.ts:29
category: "systems" # systems | ai | frontend
readTime: "12 min read"
tags: ["linux", "kernel", "devops"]
author: "Ehsan Ghaffar"
published: true # false = draft, filtered out
image: "/og-images/my-post.png" # optional
---# 1. Add file
touch content/my-new-post.mdx
# 2. Write MDX β supports GFM, code blocks with Shiki, headings with anchors---
title: "My New Post"
excerpt: "One-line hook for the blog index."
date: "2026-08-23"
dateISO: "2026-08-23"
category: "frontend"
readTime: "5 min read"
tags: ["nextjs", "tailwind"]
published: true
---
## Introduction
Hello MDX!
```ts
export const hello = "world"
```
> Tip: headings auto-get `id` + anchor links via `rehype-slug` + `rehype-autolink-headings`.No restart needed in dev β just refresh. For production, the post is auto-added to sitemap.ts:48.
import { getAllMDXPosts, getMDXPostBySlug, getAllMDXCategories } from "@/lib/mdx-utils"
import { getPostBySlug, getRelatedPosts } from "@/lib/blog-data" // legacy helper| Route | File | Description |
|---|---|---|
/ |
app/page.tsx:10 |
Hero + Projects + Lab Notes + Workbench + Footer |
/blog |
app/(public)/blog/page.tsx |
Paginated MDX listing |
/blog/[postSlug] |
app/(public)/blog/[postSlug]/page.tsx |
MDX render + JSON-LD + related posts |
/projects |
app/(public)/projects/page.tsx |
Full artifacts archive |
/workbench |
app/(public)/workbench/page.tsx |
Dedicated WIP view |
/notes |
app/(public)/notes/page.tsx |
Lab notes archive |
/introduction |
app/(public)/introduction/page.tsx |
About / intro |
/sitemap.xml |
app/sitemap.ts:4 |
Auto-generated from static + MDX routes |
/robots.txt |
app/robots.ts:3 |
allow: /, disallow: /api/, /_next/ |
Static routes use weekly/daily changeFrequency; blog posts use monthly with priority: 0.7.
- OKLCH palette β
oklch(0.5 0.22 170)primary (teal) for both light & dark, with tailored lightness. - CSS-first Tailwind v4 β no
tailwind.config.js; all tokens in@theme inlineβapp/globals.css:86. - Fonts β
Geist(sans),Geist_Mono(mono),Space_Groteskvianext/font/googlewithdisplay: swap+ CSS variables βapp/layout.tsx:9.
.glass/.glass-strongβ backdrop-blur + saturate.hover-liftβtranslateY(-4px)+ shadow.cursor-glowβ 400px radial gradient following cursor (hidden on mobile) βapp/globals.css:188.scanlinesβ subtle repeating linear gradient overlay βapp/globals.css:206- Animations:
fade-in-up,scale-in,slide-in-*,float,pulse-glow,shimmer+stagger-1..8
// app/layout.tsx:100
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem storageKey="theme-mode">Toggle via components/theme-toggle.tsx and multi-theme switcher components/theme-changer.tsx + lib/themes.ts.
- Metadata API β
title.template,openGraph,twitter,robots,icons,manifestβapp/layout.tsx:25 - JSON-LD β
WebSite+Personon homepage (app/page.tsx:12),BlogPosting+BreadcrumbListon posts βlib/structured-data.ts:3 - Sitemap & Robots β dynamic, MDX-aware
- Images β
formats: ['image/avif','image/webp'], 8 deviceSizes βnext.config.mjs:5 - Fonts β
next/fontself-hosted, no CLS - A11y β
prefers-reduced-motion, 44px touch targets,focus-visiblerings, semantic headings - Perf β Turbopack dev,
transform: translateZ(0)hardware accel, blur reduction on touch devices βapp/globals.css:140
Check deeper dives in docs/performance.md, docs/accessibility.md, docs/architecture.md.
| Command | Description | Source |
|---|---|---|
pnpm dev |
Start dev server (Turbopack) | next dev |
pnpm build |
Production build | next build |
pnpm start |
Serve production build | next start |
pnpm lint |
ESLint | eslint . |
pnpm analyze |
Bundle analyzer | next experimental-analyze |
Optimized for Vercel β zero config.
# via CLI
pnpm i -g vercel
vercel --prod
# or connect GitHub repo β Vercel auto-deploys on pushOther platforms (Netlify, Docker, Node server): pnpm build && pnpm start serves on PORT (default 3000). For Docker, see the sibling template next16-docker-tw4-starter.
Environment on Vercel: set NEXT_PUBLIC_SITE_URL to your production domain.
Contributions are welcome β especially fixes to MDX rendering, a11y, or new lab notes!
- Open an issue for larger changes
- Branch:
git checkout -b feat/my-change - Commit with clear messages
- PR with screenshots for UI changes, and ensure
pnpm buildpasses
Code style: TypeScript strict, eslint ., Tailwind v4 utilities, Radix + shadcn patterns.
MIT Β© 2024β2025 Ehsan Ghaffar β github.com/ehsanghaffar Β· eindev.ir Β· twitter.com/ehsanghaffar
Built with β€οΈ,
oklch, and a healthy obsession withnext/font.
Forge. Break. Rebuild. Repeat. β EINCODE
