Skip to content

xevrion/thy-xev

Repository files navigation

xevrion.dev

Personal portfolio and blog. Built with Next.js, self-hosted on a DigitalOcean VPS via Docker.

Stack

Framework Next.js 16 (App Router, Turbopack)
Runtime Bun
Styling Tailwind CSS v4 + CSS custom properties
Fonts Space Grotesk, Inter, Caveat (next/font/google)
Blog Fumadocs (fumadocs-mdx 14.2.7 + fumadocs-core/ui 16.6.0)
Syntax highlighting Shiki — dual light/dark theme, compiled at build time
Animations Framer Motion + GSAP + ScrollTrigger
Theme next-themes (light / dark, persisted in localStorage)
Search Fuse.js (fuzzy) + nuqs (URL state)
Icons lucide-react (UI) + react-icons (brand/tech)
Auth Clerk (@clerk/nextjs v7) — Google + GitHub sign-in
Database Neon serverless Postgres (@neondatabase/serverless)
ORM Drizzle (drizzle-orm + drizzle-kit)
Content moderation leo-profanity (leetspeak-aware) + DB-level rate limiting
Webhook verification svix (Clerk webhook signature verification)
Backend Express.js on port 3001 — Spotify OAuth token exchange only
DevOps Docker + GHCR + GitHub Actions → DigitalOcean VPS

Architecture

src/
├── app/
│   ├── (site)/          # All public pages — has navbar + footer layout
│   ├── (admin)/         # Admin panel — Clerk-gated, owner-only
│   ├── actions/         # Server actions (comments CRUD)
│   └── api/             # REST API routes + Clerk webhook
├── components/
│   ├── comments/        # CommentsSection, CommentCard, CommentComposer, CommentAvatar
│   └── admin/           # AdminDashboard, AdminEditor, AdminCommentsPanel
└── lib/
    ├── db.ts            # Neon lazy connection (Drizzle proxy)
    ├── schema.ts        # blog_posts, blog_comments, blog_comment_likes
    └── assert-admin.ts  # assertAdmin() — throws if not OWNER_CLERK_USER_ID

Env vars

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
OWNER_CLERK_USER_ID=        # your Clerk user ID — only this user gets admin access
DATABASE_URL=               # Neon connection string
CLERK_WEBHOOK_SECRET=       # from Clerk dashboard → Webhooks (set after first deploy)
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
SPOTIFY_REFRESH_TOKEN=
GITHUB_PAT=
WAKATIME_API_KEY=
NEXT_PUBLIC_OPENWEATHER_KEY=

DB commands

bun run db:push      # push schema to Neon (first-time setup or after schema changes)
bun run db:generate  # generate migration files
bun run db:migrate   # run migrations
bun run db:studio    # open Drizzle Studio (visual DB browser)

Todo

  • make basic structure

  • decide theme

  • think about markdown pages

  • first work on implementing basic functionality

    • add listening now (spotify api)
    • make the spotify thing pretty
    • add online offline status (discord via lanyard)
    • add keystrokes and mouse clicks counter and uptime
    • add current project which you're working on
    • add upcoming contests on leetcode, codechef and codeforces
      • codeforces
      • codechef (no public api)
      • leetcode (no public api)
  • make about section

    • add tech stack
  • make contact section

  • make projects section

    • basic page structure
    • some cool effects
    • current working
    • past projects
    • dynamic fetching from github repos
  • make posts section (markdown)

  • make it pretty

  • hosting

    • digital ocean $16 VPS
    • bought domain for free from name.com
  • add live coding stats (Wakatime API)

    • show time spent coding today
    • show time spent coding all time
    • show time spent coding this week
    • show language breakdown (bar chart with per-language colors, last 7 days)
  • add weather widget (OpenWeather)

    • fetch weather for Surat + Jodhpur
    • display temp, condition + small icon
    • fits the minimal theme
  • add AI-generated summaries for projects

    • generate 1–2 line tagline from project description using API
    • show tagline under each project card dynamically
  • integrate search in posts page

    • client-side fuzzy + substring search using fuse.js (no server needed)
    • searches title, description, and tags
  • add testimonials section (research other personal sites before proceeding)

  • add Resume section (pdf download + online view)

    • hosted on Google Drive, embedded via iframe (no forced download)
    • download button + /resume route + Resume nav link
  • light/dark theme

    • toggle button to switch themes
    • save preference in localStorage
  • github contribution graph

    • real data via GitHub GraphQL API
    • SVG heatmap with soft-royal-blue palette, hover tooltips, legend
    • scrolls to most recent week, works on mobile
  • reading time on posts

    • show "X min read" on post cards and at top of each post
    • computed via remarkReadingTime plugin (prose-only, skips code blocks)
  • post tags + filter

    • tags stored in YAML frontmatter, parsed by Fumadocs
    • tag chips on post cards, filter on /blogs page, shown on post page
    • horizontal scroll if too many tags
  • command palette (Cmd+K / Ctrl+K)

    • fuzzy search across pages, posts, projects
    • keyboard navigable, opens from anywhere on site
    • actions: toggle theme, open spotify song, social links
    • desktop only
  • geo-based links on gear page (US visitors get US links, India gets India links)

    • set up geo-IP detection (geoip-lite or similar, self-hosted friendly)
    • map each gear item to its US equivalent
  • /now page

    • what i'm currently learning, reading, building, listening to
    • updated manually, personal and casual tone
    • linked from hero and command palette
  • visitor count per post

    • stored in server/views.json on VPS
    • localStorage-gated on frontend (one count per browser, forever)
    • show "X reads" on post cards and post page
    • total site visitor counter on home page
  • migrate to Next.js (from React + Vite + React Router)

    • App Router, SSG, generateStaticParams, generateMetadata
    • next-themes replacing custom useTheme hook
    • useRouter/usePathname replacing react-router hooks
  • rename /posts to /blogs

  • switch to Bun as package manager + runtime

  • migrate blog to Fumadocs

    • fumadocs-mdx 14.2.7 + fumadocs-core/ui 16.6.0 on Next.js 16
    • MDX compiled at build time (no per-request rendering)
    • Shiki dual-theme syntax highlighting (github-dark / github-light)
    • remarkReadingTime plugin for accurate reading time
    • all posts converted to .mdx with YAML frontmatter
    • description field on posts
  • auto sitemap + robots.txt (Next.js metadata routes)

  • redirect shortcuts: /github, /linkedin, /twitter, /mail, /discord, /spotify

  • mouse glow effect following cursor

  • reading progress bar on blog posts (zero-lag, direct DOM writes)

  • scroll-driven CSS fade utility (.scroll-fade-x/y)

  • security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)

  • fully dockerized + GitHub Actions CI/CD to DigitalOcean VPS

    • 3 workflows: build + push image, deploy to VPS, dependabot
    • standalone Next.js output, GHCR image registry
    • docker-compose with named volume for views data
  • single-page scroll layout (Hero → About → Projects → Now → Writing → Contact)

    • anchor nav links with smooth scrolling
    • footer with all live widgets (Spotify, WakaTime, GitHub contributions, visitor count)
    • blog teaser section on homepage (4 most recent posts)
    • blocky/rectangular style (no rounded corners)
  • mobile responsive — animated nav dropdown, animated TOC

  • /projects page — dedicated page, scroll reveal on cards

  • Mark highlights, SectionDivider, SectionLabel components throughout site

  • consistent large h1 headers on /blogs, /resume, /projects

  • blogs page — tag filter, fuzzy search, pagination, per-page select, URL-synced via nuqs

  • Clerk auth for admin panel

    • src/proxy.ts — Clerk middleware (Next.js 16 convention) protecting /admin(.*)
    • admin layout checks userId === OWNER_CLERK_USER_ID — non-owners see access denied screen
    • /sign-in and /sign-up pages using Clerk's hosted <SignIn /> / <SignUp /> components
    • <ClerkProvider> in root layout
    • admin logout via Clerk signOut() replacing old cookie logout
    • src/lib/assert-admin.tsassertAdmin() server-side helper for server actions
    • /api/admin blog CRUD route now uses Clerk auth — works in production (was local-only before)
    • Clerk webhook at /api/webhooks/clerk — verified with svix, soft-deletes comments on user.deleted
    • old cookie-based admin-auth.ts + ADMIN_SECRET approach fully replaced
  • comments on blog posts

    • Neon serverless Postgres + Drizzle ORM
    • schema: blog_posts, blog_comments (with parent_id for threading), blog_comment_likes
    • drizzle.config.ts + db:push / generate / migrate / studio scripts in package.json
    • lazy DB connection — no build-time env var required (Proxy pattern in src/lib/db.ts)
    • src/app/actions/comments.ts — server actions: getComments, submitComment, deleteComment, toggleCommentLike, togglePinComment, getAllAdminComments
    • readers sign in with Clerk (Google / GitHub / email) to comment
    • threaded replies up to 2 levels deep
    • like / unlike comments (optimistic update)
    • delete own comment; owner (OWNER_CLERK_USER_ID) can delete any comment
    • owner can pin comments (pinned float to top, styled differently)
    • full optimistic UI — comment appears instantly, confirmed or rolled back after server response
    • CommentsSection injected into PostPage via prop — server-fetched initial data, client-interactive
    • admin comments panel at /admin/comments — grouped by post, search by name/content, filter pinned only, delete + pin/unpin
  • guestbook

    • /guestbook public page — sign in with Clerk, leave a short message (280 chars)
    • guestbook_entries + guestbook_likes tables in Neon schema
    • like / unlike entries (optimistic update)
    • delete own entry; owner can delete any
    • owner can pin entries
    • rate limit: 3 entries per user per 10 minutes
    • profanity filter + unicode normalization (same as comments)
    • admin guestbook panel at /admin/guestbook — search, filter pinned, delete, pin/unpin
    • Clerk webhook updated to also clean up guestbook on user.deleted
    • Guestbook link added to navbar
  • comment security

    • rate limiting — max 5 comments / 3 guestbook entries per user per 10 minutes, enforced in DB
    • profanity filter — leo-profanity with leetspeak detection, rejects at server action level
    • unicode normalization — String.normalize('NFKC') in sanitizer to block zero-width / homoglyph bypasses
    • XSS safe — comments stored as plain text, rendered via React (no dangerouslySetInnerHTML)
    • min 2 / max 1000 character validation server-side
  • Perspective API content moderation (semantic spam + toxicity detection)

    • Google Perspective API — free, no credit card, scores text for toxicity/spam/harassment
    • add as second-pass check after leo-profanity (leo catches wordlist, Perspective catches context)
    • fail-open on 429 (rate limit) — let comment through rather than blocking legit users
    • one new env var: PERSPECTIVE_API_KEY (Google Cloud project → enable Perspective API → copy key)
  • set up Clerk webhook in production

    • add endpoint https://xevrion.dev/api/webhooks/clerk in Clerk dashboard
    • select user.deleted event
    • copy signing secret → add CLERK_WEBHOOK_SECRET to GitHub Actions secrets + VPS .env
  • typing animation on hero (lowest priority)

    • cycle through "developer", "designer", "pianist", "iit student"
  • dual favicons (light/dark SVG via prefers-color-scheme)

  • improve blog post formatting — inline code styling, image rounding

  • GSoC / Experience section

  • blog post editing workflow — decided to keep editing locally (VS Code → commit → push → CI deploys). Admin editor on prod works but requires a redeploy to reflect changes due to Fumadocs static generation at build time. For typo emergencies use GitHub web editor directly.

  • background moves with page scroll (not fixed)

  • /blog/:path*.mdx route (LLM-friendly raw MDX)

  • add testimonials section

  • geo-based links on gear page (US vs India links per item)

About

personal website~

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors