Dotdeck (Placeholder)
A hub for sharing copy-pastable developer decks — dotfiles, CLI tweaks, and snippets.
Full-stack web app that makes it easy to publish, browse, search, and discuss scoped configuration snippets called "decks".

Quick Stats
Dotdeck
Dotdeck is live. The write-up below covers the architecture, feature decisions, and lessons from building the first production full-stack project I've shipped solo.
My previous approach to sharing dotfiles was a monolithic repo nobody could navigate. Dotdeck trims that pain by focusing on standalone, copy-pastable "decks" you can browse, search, fork, and discuss without peeling through hundreds of commits.
Problem space
Finding a single snippet buried in a six-year-old forum thread is exhausting. GitHub Gist is too loose — no tagging, no discovery, no discussion adjacent to the code. Dotdeck fills the gap:
- Decks are intentionally scoped: title, tags, description, and one or more code blocks.
- Built-in reactions and threaded comments keep discussion near the snippet.
- Full-text search with pagination makes discovery fast.
Feature overview

Core features shipped:
- Authentication with JWT (register / login) and role-based access control — admin, mod, user.
- Like / dislike counts with optimistic UI updates via TanStack Query.
- Threaded comments with real-time count badges.
- Image uploads (WebP, compressed via Sharp) for deck thumbnails.
- Admin panel for managing tags, decks, users, and audit logs.
- Fully documented OpenAPI 3.0 spec via Swagger at
/docs.
Architecture
| Layer | Tech |
|---|---|
| Front-end | Next.js 14, React 18, TypeScript, Tailwind, shadcn/ui, TanStack Query |
| Back-end | Express 4, MySQL, multer + Sharp, JSON Web Tokens |
| Dev / ops | Campus:Cloud Node instances, pnpm workspace toolchain |
The monorepo splits into apps/web (Next.js) and apps/api (Express). Shared types live in packages/types so API responses and client expectations can't drift.
Deck schema
Authentication flow
- Client POSTs credentials to
/auth/login. - API validates against
bcrypthash, issues a short-livedaccessTokenand ahttpOnlyrefreshTokencookie. - TanStack Query's
defaultOptions.queries.onErrorintercepts 401s and triggers a silent token refresh before retrying.
Image pipeline
Uploaded thumbnails go through a Sharp pipeline before storage:
# resize to 800×600 max, convert to WebP at quality 80
sharp(buffer)
.resize(800, 600, { fit: 'inside' })
.webp({ quality: 80 })
.toBuffer()
Average upload size dropped from ~1.2 MB (raw PNG) to ~85 KB (WebP) — a 93 % reduction with no perceptible quality loss at card thumbnail sizes.
What I'd do differently
- MySQL → PostgreSQL — full-text search in MySQL required custom FULLTEXT indexes that fight the ORM. Postgres
tsvectoris cleaner. - Separate read replicas — the admin audit log query is expensive and doesn't need to hit the write primary.
- Rate limiting from day one — added it in week three after a stress test revealed the comment endpoint had no throttling.
Using inline code for the key design tension: keeping the refreshToken in an httpOnly cookie means the client can't read it — but it also can't detect expiry proactively, which is why the interceptor retry pattern was necessary.
Building the whole stack yourself means every trade-off is yours to own — which is exactly the point.
Have thoughts?
Curious what others see or think
Feel free to reach out or leave feedback
Share FeedbackPrefer email? joshuatjhie@pm.me