Skip to content

kaiiiichen/kaichen.dev

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

187 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

kaichen.dev

CI Vitest ESLint
Next.js 16 React 19 TypeScript 5 Tailwind CSS 4 Node.js 20
Deployed on Vercel License GPL-3.0 Dependabot kaichen.dev

Personal website of Kai Chen — production: kaichen.dev.

This repository is a Next.js 16 application using the App Router, React 19, TypeScript, and Tailwind CSS 4. It is deployed on Vercel.

Resource URL
Production site https://kaichen.dev
Source https://github.com/kaiiiichen/kaichen.dev

Table of contents

  1. Overview
  2. Requirements
  3. Quick start
  4. npm scripts
  5. Repository layout
  6. Technology stack
  7. Routes and features
  8. API routes
  9. Environment variables
  10. External integrations
  11. Local development
  12. Testing
  13. CI, Dependabot, and auto-merge
  14. Git hooks
  15. Deployment
  16. Documentation map
  17. Forking this project
  18. License

Overview

The site combines:

  • A marketing-style home page (identity, social icons with brand-colored hovers, Spotify listening card, Berkeley weather, GitHub pinned repositories via GraphQL with a static fallback, and Substack headlines).
  • Dynamic data from Spotify, GitHub, Open-Meteo, and optional Supabase-backed listening history.
  • Notes are hosted externally on Notion (via the top navigation link).
  • Optional observability via Sentry (client, server, edge) and Vercel Analytics / Speed Insights.

UI / typography: The root body uses Geist Sans (font-sans). Nunito (via @fontsource/nunito) is used for the top nav, magazine-style cards (.mag-card / .mag-label), and most English copy inside those surfaces so the chrome stays consistent. Theme defaults to light when unset; .dark on <html> comes from the theme script + provider.

Now playing: The client hook app/hooks/use-now-playing.ts polls GET /api/spotify/now-playing about every 10s (cache: "no-store").

There is no middleware.ts (or proxy.ts) in this repo — every route is publicly accessible and rendered by the App Router directly.


Requirements

Tool Version / notes
Node.js 20.x (matches CI and @types/node)
npm 9+; lockfile is package-lock.json — use npm ci for reproducible installs

Quick start

git clone https://github.com/kaiiiichen/kaichen.dev.git
cd kaichen.dev
npm install
cp .env.example .env.local

Edit .env.local following Environment variables. You do not need every key to run the app locally; missing keys typically degrade or hide features rather than crash the build (exceptions: pages that import Supabase at module scope use placeholder values in CI — see below).

Start the dev server:

npm run dev

Open http://localhost:3000.

Important: dev and build both pass --webpack to Next.js. Keep local and production behavior aligned.


npm scripts

Script Command Purpose
dev next dev --webpack Local development with Webpack.
build next build --webpack Production bundle (also runs type checking as part of Next).
start next start Serve the last build output (run build first).
lint eslint ESLint across the repo (eslint.config.mjs).
typecheck tsc --noEmit TypeScript without emitting JS.
test vitest run Unit tests once (CI uses this).
test:watch vitest Vitest in watch mode.
postinstall git config core.hooksPath .githooks … Points Git at .githooks/ so the prepare-commit-msg hook runs after npm install (see Git hooks).

Before opening a PR, run the same sequence as CI:

npm run lint && npm run typecheck && npm run test && npm run build

Repository layout

High-level map (not every file):

kaichen.dev/
├── app/                          # App Router
│   ├── layout.tsx                # Root layout: fonts, theme script, Nav, Providers, Analytics
│   ├── page.tsx                  # Home
│   ├── globals.css
│   ├── global-error.tsx          # Root error boundary + Sentry
│   ├── opengraph-image.tsx       # OG image for /
│   ├── about/                    # Bio / CV-style page + OG
│   ├── projects/                 # Projects + GitHub heatmap + OG
│   ├── api/                      # Route handlers (Spotify, GitHub, weather)
│   ├── components/               # UI: nav, mobile-nav, cards, theme, weather, listening, GitHub heatmap, …
│   ├── hooks/                    # use-now-playing.ts (Spotify poll)
│   └── lib/                      # og.tsx, substack RSS, GitHub pinned repos (GraphQL)
├── lib/                          # Shared server-oriented helpers + Vitest tests
│   ├── now-playing.ts            # Types for now-playing payload
│   ├── spotify-now-playing-helpers.ts
│   ├── spotify-access-token.ts
│   ├── listening-supabase.ts
│   ├── weather-open-meteo.ts
│   └── *.test.ts
├── mdx-components.tsx            # MDX element mapping
├── next.config.ts                # Webpack config + withSentryConfig
├── instrumentation.ts            # Sentry Node/Edge registration
├── instrumentation-client.ts     # Sentry browser + router transition hooks
├── sentry.server.config.ts
├── sentry.edge.config.ts
├── vitest.config.ts
├── eslint.config.mjs
├── .githooks/                    # Git hooks (co-author trailer)
├── .github/
│   ├── workflows/                # ci.yml, auto-merge.yml
│   ├── dependabot.yml
│   ├── ISSUE_TEMPLATE/
│   └── pull_request_template.md
├── .env.example
├── AGENTS.md                     # AI agent / automation git rules
├── CLAUDE.md                     # Short context for Claude Code (points here + AGENTS)
├── CONTRIBUTING.md
├── SECURITY.md
├── CODE_OF_CONDUCT.md
└── LICENSE                       # GPL-3.0

Technology stack

Layer Choices
Framework Next.js 16.2 (App Router), React 19, TypeScript 5
Styling Tailwind CSS 4 (@tailwindcss/postcss), shared UI tokens + magazine cards in app/globals.css (.mag-card, .mag-label)
Content MDX / Markdown page extensions are enabled in next.config.ts via @mdx-js/loader (+ remark-gfm, remark-math, rehype-katex, rehype-highlight); mapping in root mdx-components.tsx. There are no app/**/*.mdx routes in-tree yet—add files when you want long-form pages.
Fonts @fontsource/* (Nunito, JetBrains Mono), geist (Geist Sans / Mono as CSS variables on <html>, default font-sans on <body>)
Data Supabase (@supabase/supabase-js) — optional listening history DB writes (service role) for /api/spotify/now-playing
Monitoring @sentry/nextjs (optional DSN), Vercel Analytics + Speed Insights
Testing Vitest 4

Pinned versions are in package.json.


Routes and features

Route What it does
/ Identity block, social links (mailto + GitHub, LinkedIn, X, Spotify), Listening + Location cards (Spotify + Open‑Meteo: temp, condition, feels-like, humidity, local America/Los_Angeles clock via berkeley-time.tsx), pinned GitHub repos (GraphQL + fallback list), Substack RSS snippets
/about Education, experience, courses, volunteering
/projects Project cards + GitHub contribution calendar (client component, data from /api/github/contributions)

External nav (no in-app route): the main nav includes NotesNotion and BlogSubstack; there is no /notes or /blog route in this repo.

Open Graph: several routes ship opengraph-image route handlers for social previews. Set metadataBase in app/layout.tsx if you see build warnings about resolving OG image URLs.


API routes

All handlers live under app/api/.

Method & path Behavior Caching / notes
GET /api/spotify/now-playing Spotify me/player/currently-playing + recently-played; optional service-role merges legacy listening_stats rows into spotify:<trackId>; writes listening_* while is_playing Cache-Control: public, s-maxage=10, stale-while-revalidate=5; SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, SPOTIFY_REFRESH_TOKEN; in-memory lastKnownTrack fallback
GET /api/github/contributions GraphQL contribution calendar + REST search for latest commit + REST repo metadata for star counts dynamic = force-dynamic; Cache-Control: no-store; requires GITHUB_TOKEN
GET /api/github/stars?repo=owner/name Returns stargazers_count and archived for a repo revalidate = 3600; optional GITHUB_TOKEN for rate limits
GET /api/weather Open-Meteo current conditions for fixed Berkeley coordinates (temperature_2m, weathercode, apparent_temperature, relative_humidity_2m, hourly rain chance) fetch with next.revalidate = 600; parsed in lib/weather-open-meteo.ts

Environment variables

Copy .env.example to .env.local. Never commit real secrets.

Always safe to document (names only)

Variable Role
NEXT_PUBLIC_SUPABASE_URL Supabase project URL. Paired with SUPABASE_SERVICE_ROLE_KEY inside /api/spotify/now-playing for the optional listening history.
SUPABASE_SERVICE_ROLE_KEY Server-only. Used by /api/spotify/now-playing for DB reads/writes against listening_history / listening_stats — keep off the client bundle.
SPOTIFY_CLIENT_ID / SPOTIFY_CLIENT_SECRET / SPOTIFY_REFRESH_TOKEN Spotify app + user refresh token (scopes: user-read-currently-playing, user-read-recently-played). If unset, the route falls back to memory + DB for “last played.”
GITHUB_TOKEN Fine-grained or classic PAT for GitHub API (contributions + stars + pinned repos on the home page). If missing, contribution/stars features may error or return empty data; pinned projects fall back to a static list in app/lib/github-pinned.ts.
GITHUB_LOGIN Optional. GitHub username for pinned repositories and related API calls (defaults to kaiiiichen if unset). Set when forking so the home page shows your pins.

Sentry (optional)

Variable Role
NEXT_PUBLIC_SENTRY_DSN / SENTRY_DSN Error reporting; see instrumentation.ts and Sentry configs.
SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT Build-time source map upload for readable stack traces in Sentry (configure on Vercel, not in git).

Vercel CLI (optional)

vercel env pull .env.vercel.check

That path is gitignored — do not commit it.

CI builds without live Supabase

CI does not need any real Supabase keys to build — every Supabase client in this repo is constructed lazily inside a function body, so next build succeeds without NEXT_PUBLIC_SUPABASE_* set. See .github/workflows/ci.yml.


External integrations

Service Use in this repo
Spotify Web API Current + recently played track
GitHub GraphQL Contribution calendar
GitHub REST Repo stars, commit search
Open-Meteo Weather (no API key); Berkeley lat/long in app/api/weather/route.ts
Supabase Optional listening_history / listening_stats writes (service role) for /api/spotify/now-playing
Substack RSS Home page “latest posts” (app/lib/substack.ts)

Local development

  • Node 20, npm install then npm run dev.
  • Supabase: only /api/spotify/now-playing uses Supabase (service role) for the optional listening history. The site builds without Supabase env; when it is unset, the route skips DB reads/writes and still uses Spotify if SPOTIFY_* is configured.

Common issues

Symptom Things to check
GitHub widgets empty GITHUB_TOKEN set and not expired; API rate limits.
"Recently played" never persists across deploys NEXT_PUBLIC_SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY set; tables listening_history / listening_stats exist with the expected columns.
Sentry noisy locally DSN unset disables reporting; or lower sample rate in instrumentation-client.ts.

Testing

Unit tests use Vitest and live under lib/*.test.ts: lib/substack-rss.test.ts, lib/weather-open-meteo.test.ts, lib/spotify-now-playing-helpers.test.ts.

npm run test
npm run test:watch

There are currently no Playwright/E2E tests in this repo; manual browser checks matter for layout and visual polish.


CI, Dependabot, and auto-merge

Triggers on push and pull_request to main:

npm cilinttypechecktestbuild on ubuntu-latest, Node 20, with npm cache.

  • npm and github-actions ecosystems, weekly (Monday 09:00 America/Los_Angeles).
  • Grouped updates (fonts, Sentry, Supabase, MDX-related, Vercel, types, catch-all minor/patch).
  • Ignored semver-major bumps for core tooling (next, react, eslint, typescript, tailwindcss, …) so those upgrades stay manual.

Runs only when the PR author is dependabot[bot]:

  1. Reads semver classification via dependabot/fetch-metadata.
  2. For patch and minor updates: enables gh pr merge --auto --squash (respects branch protection when checks pass).
  3. On open / reopen, posts an idempotent PR comment that includes the official @dependabot squash and merge line (documentation + redundancy; primary merge path is still GitHub auto-merge).

pull_request types include synchronize so Dependabot force-pushes re-enable auto-merge. Concurrency is scoped per PR number to avoid overlapping runs.


Git hooks

After npm install, postinstall runs:

git config core.hooksPath .githooks

.githooks/prepare-commit-msg appends:

Co-authored-by: Claude <noreply@anthropic.com>

to non-merge commits via git interpret-trailers (idempotent). Automation that cannot run hooks should add the same trailer manually — see AGENTS.md.


Deployment

  1. Connect the GitHub repository to Vercel.
  2. Set environment variables in the Vercel project (production + preview as needed), especially GITHUB_TOKEN, Spotify keys, and the Supabase variables if you want listening history persistence.
  3. Pushes to main typically deploy production; preview deployments use PR branches.

Manual CLI (after vercel link):

vercel --prod

Documentation map

File Audience Contents
README.md (this file) Everyone Setup, architecture, APIs, env, CI
CONTRIBUTING.md Human contributors How to PR, conventions, CI parity
AGENTS.md AI agents / automation Branch + PR only, co-author trailer, secrets
CLAUDE.md Claude Code Short pointer + stack summary
SECURITY.md Security researchers How to report issues responsibly
CODE_OF_CONDUCT.md Contributors Contributor Covenant
.env.example Developers Variable names and brief comments
.github/pull_request_template.md PR authors Checklist

Cursor-specific rules live under .cursor/rules/ (IDE-only, not required reading for all contributors).


Forking this project

Replace at minimum:

Area Where to look
Copy, links, projects list, social URLs app/page.tsx, app/projects/page.tsx, app/about/page.tsx
Spotify OAuth app + refresh token Spotify Developer Dashboard; env SPOTIFY_* consumed in lib/spotify-access-token.ts
GitHub login / repos / pins app/api/github/contributions/route.ts, app/components/project-stars.tsx, app/lib/github-pinned.ts, env GITHUB_LOGIN
Supabase tables lib/listening-supabase.ts, app/api/spotify/now-playing/route.ts, Supabase dashboard (listening_history, listening_stats)
Substack feeds app/lib/substack.ts
Weather location app/api/weather/route.ts, weather UI components
Theme / fonts app/layout.tsx, app/globals.css, app/components/theme-provider.tsx

Keep LICENSE compliance if you redistribute (GPL-3.0).


License

This project is licensed under the GNU General Public License v3.0 — see LICENSE.


Security

Please read SECURITY.md before reporting vulnerabilities.

About

my personal space at a corner of human made internet :D

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages