Termfolio
VAULTwa1939

Termfolio

A terminal/CLI-themed personal portfolio, blog, and digital business card. Syncs directly with your Obsidian vault - No database required. Just fork it, paste your CV into any AI to generate your config file, and deploy in minutes. Built with Next.js 15, React 19, TypeScript, and Tailwind CSS

termfolio

termfolio

Your portfolio should boot like a terminal, not load like a Squarespace.

Write in Obsidian. Push to GitHub. Your site updates.
No database. No CMS. No writing in two places.


Next.js React TypeScript Tailwind Obsidian Vercel


Stars Forks License Last Commit Visitors

Live Demo · Deploy Your Own · Full Tutorial


Deploy in 3 Minutes

No local setup required. Click, paste, done.

StepActionTime
1Click Deploy with Vercel below — it forks the repo and creates your project30s
2Vercel asks for 3 env variables — get them from resend.com (free tier)90s
3Click Deploy — your site is live60s

Deploy with Vercel

How to get the 3 env variables (2 minutes)
  1. Sign up at resend.com (free — 3,000 emails/month)
  2. RESEND_API_KEY — API Keys → Create API Key → copy
  3. RESEND_AUDIENCE_ID — Audiences → Create Audience → copy the ID
  4. NOTIFY_SECRET — Any random string. Generate one: openssl rand -hex 32

After deploy, clone your fork and edit one filecontent/site.ts — to make it yours. Push. Done.

Want a full walkthrough with screenshots? Read the step-by-step tutorial.


See It in Action

HomeBlog
Home — Boot sequence, interactive terminal, halftone profileJournal — Search, tag filters, writing activity heatmap
Blog PostAbout
Blog Post — 3 reading themes, ToC, focus mode, progress barAbout — Experience timeline, skills, certifications
ContactMobile
Contact — Cal.com scheduling embed, newsletter signupCard — Digital business card with vCard, QR, WhatsApp

Why termfolio?

Most portfolio templates make you choose: look good or easy to maintain.

Traditional portfoliotermfolio
Write contentCMS dashboardObsidian / VS Code / any editor
Content storageDatabase (Postgres, Notion, Contentful)Plain .md files in your repo
Write in one placeNo — CMS + codeYes — just markdown
Vendor lock-inTied to CMS providerZero — it's just files
Works offlineNoYes
DeployComplex pipelinegit push
CustomizeDig through 50+ filesEdit one file (content/site.ts)

What's Inside

The Best Reading Experience

We obsessed over reading UX so your visitors actually finish your posts:

  • 3 reading themes — Terminal (dark), Light, and Sepia. Readers switch mid-article without losing their place
  • Adjustable font size — Small, medium, large. Readers pick what's comfortable
  • Focus mode — Hides everything except the article. No nav, no sidebar, no distractions
  • Reading progress bar — Shows how far through the post they are
  • Table of contents — Auto-generated from headings, highlights current section as you scroll
  • Estimated reading time — Shown before they start
  • Syntax highlighting — Code blocks with proper language coloring
  • Full RTL/Arabic support — Set language: "ar" in frontmatter and the entire post flips — layout, fonts, everything

14 Built-in Terminal Commands

Your visitors won't just read — they'll play. The home page terminal is a real command parser:

CommandWhat it does
snakeClassic Snake game — playable right in the terminal
pokedexBrowse Pokemon with stats, types, and pixel art
typing-testSpeed typing challenge with WPM tracking
starmapInteractive constellation map based on your coordinates
worldmapSVG world map highlighting your city
jsonPaste and format/validate JSON instantly
dashboardSystem dashboard with live clock and stats
base64Encode/decode Base64 strings
wordcountCount words, characters, and lines
epochConvert Unix timestamps to human dates
uuidGenerate random UUIDs
whoamiShows your identity (reads from config)
skillsShows your skills (reads from config)
themeToggle light/dark mode

Every command reads from your config — whoami outputs your name, starmap shows your sky.

Write in Obsidian, Publish Everywhere

Obsidian (write) → content/posts/*.md → git push → Live site

Your markdown files are the blog. No database. No CMS. No API calls to fetch content.

  • Symlink your vault → posts update when you save in Obsidian
  • GitHub Action included → auto-syncs from a separate vault repo on push
  • Works with any editor — VS Code, Vim, iA Writer — if it saves .md files, it works

See Obsidian Integration for setup instructions.

Comments Powered by GitHub

Readers comment on your posts using their GitHub account. Comments live in your repo's Discussions tab — no database, no moderation dashboard, no third-party service.

Setup takes 2 minutes:

  1. Go to giscus.app
  2. Enter your repo name — it checks if Discussions are enabled
  3. Pick a category (use "Announcements")
  4. Copy the 4 values it gives you
  5. Add them to your .env.local:
NEXT_PUBLIC_GISCUS_REPO=your-username/termfolio
NEXT_PUBLIC_GISCUS_REPO_ID=R_xxxxxxxxxx
NEXT_PUBLIC_GISCUS_CATEGORY=Announcements
NEXT_PUBLIC_GISCUS_CATEGORY_ID=DIC_xxxxxxxxxx

Comments appear at the bottom of every blog post. Moderate them from GitHub Discussions.

Everything Else

  • Digital business card — Shareable /card page with vCard download, QR code, WhatsApp, and Apple Wallet pass. Data lives in a single YAML file (content/card.md) — edit in Obsidian, no code changes needed
  • Newsletter — Email subscriptions + new-post notifications via Resend (free: 3,000 emails/month)
  • Halftone image effect — Your profile photo renders as a canvas-based halftone
  • Animated starfield — Star field background on the home page
  • Writing heatmap — GitHub-style contribution graph for your blog activity
  • Cal.com embed — Scheduling widget on the contact page
  • Spotify widget — Links to your music profile
  • SEO optimized — Dynamic OG images, sitemap, robots.txt, structured metadata
  • Vercel Analytics — Built-in analytics and speed insights
  • Security hardened — HSTS, CSP headers, rate limiting, timing-safe auth, input validation

Configure from One File

Open content/site.ts and make it yours. This single file controls your entire site:

export const siteConfig = {
  name: "Your Name",           // site title, metadata, emails, footer
  handle: "you",               // terminal prompt, top bar
  tagline: "your · tagline",   // below ASCII art
  email: "you@example.com",    // contact page, comment fallback
  siteUrl: "https://you.com",  // metadata, sitemap, emails

  // Terminal commands read from these
  whoami: { focus: "what you do", status: "what you're up to" },
  terminalSkills: ["skill1 // skill2 // skill3"],

  // Your location powers the star map and world map
  coordinates: { lat: 40.7128, lon: -74.0060, label: "New York" },

  // Social links in the nav bar
  socials: {
    github: { url: "https://github.com/you", label: "GitHub", icon: "</>" },
    linkedin: { url: "https://linkedin.com/in/you", label: "LinkedIn", icon: "[in]" },
  },

  // Generate ASCII art at patorjk.com/software/taag
  asciiArt: { home: [...], about: [...] },

  // About page
  bio: [...], experience: [...], skills: [...], certifications: [...],
}
Full field reference
FieldControls
nameSite title, metadata, email sender, footer
handleTerminal prompt, top bar display
titleMeta title, about page header
taglineText below ASCII art on home page
descriptionMeta/OG description across all pages
emailContact page, comment fallback
siteUrlMetadata, sitemap, robots.txt, email links
twitterHandleTwitter/X card metadata
calUrl / calEmbedUrlContact page calendar widget
spotifyUrlMusic widget link
coordinatesStar map location, world map pin
asciiArt.home / asciiArt.aboutASCII banners (generate here)
socialsNav bar links (GitHub, LinkedIn, X, etc.)
whoamiTerminal whoami command output
terminalSkillsTerminal skills command output
terminalPromptShell prompt (e.g. root@you:~)
developedByFooter attribution (links to this repo)
customizedByYour attribution — { name: "You", url: "..." }
bio, stats, experience, skills, certifications, credentialsAbout page content

Blog Posts

Create .md files in content/posts/. That's your entire CMS:

---
title: "My First Post"
date: "2026-01-15"
excerpt: "A brief description for cards and SEO"
tags: ["topic", "another"]
status: "published"
language: "en"
---

Your markdown content here. Supports GFM: tables, task lists,
strikethrough, footnotes, syntax-highlighted code blocks.
FieldRequiredNotes
titleYesPost title
dateYesISO date (YYYY-MM-DD)
statusYes"published" or "draft" (drafts are hidden)
excerptNoUsed in cards, SEO, and email notifications
tagsNoArray of strings for filtering
languageNo"en" (default) or "ar" for full RTL Arabic
coverImageNoPath relative to public/
authorNoDefaults to siteConfig.name

Obsidian Integration

Option A: Copy files (simplest)

Copy .md files from your Obsidian vault to content/posts/.

Option B: Symlink (best for local dev)

Point content/posts/ at your vault. Posts update live when you save in Obsidian:

# macOS/Linux
ln -s ~/obsidian-vault/published content/posts

# Windows (PowerShell as Admin)
New-Item -ItemType SymbolicLink -Path content\posts -Target C:\Users\you\obsidian-vault\published
Option C: GitHub Action (fully automated)

A ready-made workflow is included at .github/workflows/sync-obsidian.yml. It syncs posts from a separate Obsidian vault repo automatically.

Setup:

  1. Store your Obsidian posts in a separate GitHub repo (e.g. your-username/obsidian-vault)
  2. Create a Personal Access Token with repo scope
  3. In this repo → Settings → Secrets → Actions, add:
    • VAULT_REPOyour-username/obsidian-vault
    • VAULT_PAT — your PAT
    • VAULT_PATH — folder inside the vault repo (default: published)
  4. Runs daily at 6 AM UTC, or trigger manually from the Actions tab

Auto-trigger on vault push — add this workflow to your vault repo:

name: Trigger site sync
on:
  push:
    paths: ['published/**']
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - run: |
          curl -X POST \
            -H "Accept: application/vnd.github.v3+json" \
            -H "Authorization: token ${{ secrets.SITE_REPO_PAT }}" \
            https://api.github.com/repos/YOUR_USERNAME/termfolio/dispatches \
            -d '{"event_type":"sync-obsidian"}'

Now every vault push auto-updates your site.

Obsidian frontmatter template
---
title: "{{title}}"
date: "{{date:YYYY-MM-DD}}"
author: "Your Name"
excerpt: ""
coverImage: ""
tags: []
status: "draft"
language: "en"
---

Environment Variables

VariableRequiredDescription
RESEND_API_KEYYesResend API key (free: 3,000 emails/month)
RESEND_AUDIENCE_IDYesResend audience ID (Audiences → Create → copy ID)
NOTIFY_SECRETYesAny random string — protects the notification endpoint
RESEND_FROM_EMAILNoSender address (default: Name <noreply@yourdomain.com>)
NEXT_PUBLIC_SITE_URLNoOverride site URL (default from siteConfig.siteUrl)
NEXT_PUBLIC_NASA_API_KEYNoNASA API key for APOD widget
NEXT_PUBLIC_GISCUS_REPONoRepo name for Giscus comments
NEXT_PUBLIC_GISCUS_REPO_IDNoGiscus repo ID
NEXT_PUBLIC_GISCUS_CATEGORYNoGiscus category (use "Announcements")
NEXT_PUBLIC_GISCUS_CATEGORY_IDNoGiscus category ID
WALLETWALLET_API_KEYNoWalletWallet API key for Apple Wallet passes on /card

Tech Stack

LayerTechnology
FrameworkNext.js 15 (App Router)
UIReact 19, Tailwind CSS 3, CSS design tokens (--term-*)
ContentLocal Markdown with gray-matter — no database
Markdownunified → remark-parse → remark-gfm → remark-rehype → rehype-slug → rehype-highlight → rehype-react
EmailResend API (newsletter + new-post notifications)
CommentsGiscus (GitHub Discussions — no database)
AnalyticsVercel Analytics + Speed Insights
FontsIBM Plex Mono (UI), Source Serif 4 (reading), Noto Naskh Arabic (RTL)

Project Structure

content/
  site.ts               ← THE file. Your entire site config.
  posts/*.md            ← Your blog posts. Drop markdown, done.
  card.md               ← Digital business card data (YAML frontmatter).

app/
  page.tsx              Home (boot terminal, hero, recent posts)
  about/page.tsx        About/resume page
  blog/page.tsx         Journal (search, heatmap, tag filters)
  blog/[slug]/          Blog post (3 themes, ToC, focus mode, comments)
  contact/page.tsx      Contact (Cal.com embed, newsletter)
  card/page.tsx         Digital business card (vCard, QR, WhatsApp)
  api/subscribe/        Newsletter subscription endpoint
  api/notify/           New-post notification endpoint
  api/card/vcard/       vCard (.vcf) download endpoint
  api/card/wallet/      Apple Wallet pass endpoint

components/             30+ components including:
  boot-terminal.tsx     Interactive terminal with 14 commands
  reading-controls.tsx  3 themes, font size, focus mode, progress bar
  writing-heatmap.tsx   GitHub-style contribution graph
  halftone-image.tsx    Canvas halftone image effect
  star-map.tsx          Interactive constellation renderer
  world-map.tsx         SVG world map with location pin

Dev Commands

npm run dev        # Development server
npm run build      # Production build
npm run lint       # ESLint
npm run typecheck  # TypeScript check
npm run check      # Lint + typecheck + build

Contributing

Contributions welcome:

  1. Fork the repo
  2. Create your branch (git checkout -b feat/cool-feature)
  3. Commit (git commit -m 'feat: add cool feature')
  4. Push (git push origin feat/cool-feature)
  5. Open a Pull Request

Report bugs or request features →

Star History

Star History Chart

Attribution

Keep the "developed by" footer link or add your own name next to it in content/site.ts:

customizedBy: { name: "Your Name", url: "https://your-site.com" }
// → "developed by waleed alhamed · customized by your name"

License

MIT — use it however you want.


If this helped you build something cool, give it a :star:

Built by Waleed Alhamed

Related

How to Install

  1. Download the ZIP or clone the repository
  2. Open the folder as a vault in Obsidian (File → Open Vault)
  3. Obsidian will prompt you to install required plugins

Stats

Stars

0

Forks

0

License

MIT

Last updated 20d ago

Tags

blogbusiness-cardclidigital-cardlinktree-alternativemarkdownnextjsobsidainopen-sourcepersonal-websiteportfolioportfolio-websitereacttailwindcsstemplateterminaltypescriptwave