injoon5.com

2024

·

2
min read

This site — blog, projects, comments, likes, and a Convex-backed /now page.

SvelteKitConvexVercel

What you’re looking at

You’re reading this on it. injoon5/web is my personal site — blog, project writeups, comments, likes, and the /now page. Prerendered where possible, realtime where it matters.

Frontend

SvelteKit with mdsvex for Markdown content. Blog posts and project pages live as .md files with frontmatter; reading time gets computed at build time by a Remark plugin that counts words (English) or characters (Korean).

Multilingual support: blog posts and projects can exist in both en/ and ko/ directories. The slug page loads whichever languages are published and shows a pill toggle.

The homepage fetches two external JSON files client-side on mount — now-playing.json and photos data from the same repo. No server needed; raw GitHub URLs have no CORS restrictions.

Convex backend

Anything live goes through Convex (convex-svelte subscriptions on the client):

TablePurpose
commentsThreaded comments per page URL, max depth 2
commentVotesUp/down votes keyed on SHA-256 IP hash
likesPage-level like toggles
bannedIpsIP ban list
nowPageEditable /now page content

Comments are sorted by score (upvotes − downvotes), then recency. Passwords are bcrypt-hashed for edit/delete. Rate limiting runs inside Convex via @convex-dev/rate-limiter — survives cold starts, applies to both HTTP routes and direct mutations. Admin requests with a valid ADMIN_SECRET bypass everything.

The admin dashboard (/admin) uses cookie auth + x-admin-secret header for API calls. Can reply to comments, soft/hard delete, ban IPs, view IP hashes.

API layer

SvelteKit API routes (/api/comments, /api/likes, /api/admin/*) sit between the browser and Convex and do the unglamorous middle-layer work: IP hashing, Zod validation, bcrypt checks, and translating Convex errors into proper HTTP status codes (a 429 with Retry-After when you hit a rate limit).

OG images

Built-in OG generation at /api/og — satori templates in src/lib/og/templates.js at 1200×630 with a dark dot-grid background. Separate layouts for blog posts, projects, and listing pages.

Why I keep rebuilding it

This is version… many. Raster, Next.js iterations, Ghost themes, the whole oij-web era. I migrated comments off SQL + Redis specifically because I wanted Convex realtime subscriptions without managing infrastructure.

It’s never done. But it’s mine, and that’s the point.