Build. Learn. Document. โ MDX-rendered articles, GitHub-gated comments, and an OAuth-protected admin CMS in a neo-brutalist design system.
๐ Quick Start ยท โ๏ธ Environment ยท ๐บ๏ธ Routes & API ยท ๐ ๏ธ Admin CMS ยท โ๏ธ Deploy
- โจ Features
- ๐งฐ Tech Stack
- ๐ Prerequisites
- ๐ Quick Start
- โ๏ธ Environment Variables
- ๐ Setting Up GitHub OAuth
- ๐๏ธ Setting Up MongoDB
- ๐ Available Scripts
- ๐ Project Structure
- ๐บ๏ธ Routes
- ๐ ๏ธ Admin CMS Guide
- ๐ Content & MDX Pipeline
- ๐ฌ Comments System
- ๐จ Design System
- ๐ฆ Build & Run in Production
- โ๏ธ Deploying to Vercel
- ๐ฉบ Troubleshooting
- Home โ featured article, latest posts, featured-project spotlight (wording follows the project's status: Currently building while
active, Recently shipped onceshipped), and newsletter callout. The site header carries the same status-aware Now building / Just shipped chip on desktop. - Articles โ filterable, paginated index (category + search) at
/articles. - Article detail โ server-rendered MDX (GitHub-flavored markdown), optional cover image figure, Shiki syntax highlighting, auto-generated table of contents, reading time, share links, font-size toggle, view counts, and category-aware prev/next navigation (older = previous, newer = next, same-category first, any-category fallback, deterministic ordering for identical timestamps). A post never repeats on the page: prev/next claim their slots first and More From The Journal fills with other posts (max 3).
- Comments โ custom, GitHub-gated comment threads under every article and project case study (see Comments System).
- Slug-first breadcrumbs โ detail pages show the entry's (editable) URL slug, humanised (
my-short-urlโMy Short Url), in the breadcrumb chip instead of the long title, so it never wraps or cuts mid-sentence. - Projects โ filterable browser at
/projects(status + tech filters, deep-linkable as/projects?tech=Next.js) plus article-like case-study pages: an identical byline bar (author, date, computed reading time + word count, live view count, font-size toggle, share links), cover image, scroll-spy TOC rail with reading-progress meter, author card, comments, a More From The Workbench related-projects footer, and Live Demo / GitHub actions (top-right on desktop, full-width on mobile). - About โ author bio, journey timeline, toolbelt, and the featured-build On The Bench card (also status-worded), all editable from the CMS.
- Skeleton loading โ the article/project detail routes stream behind their own
loading.tsxfallbacks; the list pages render their shell instantly and stream data inside a page-level<Suspense>boundary (a parent-segmentloading.tsxwould also wrap the[slug]children and flash the wrong skeleton on home โ detail navigation). - Dark mode โ light/dark toggle persisted in
localStorage(buildlog-theme) and applied before first paint; default is light. - Command-palette search โ global search over published posts and projects (Ctrl/Cmd+K), backed by the public
/api/searchendpoint. - Toasts โ brutalist ink-bordered confirmations for every action (publish, archive, feature, delete, comment, reply, like, edit, link copy, history clear, sign in/out). Desktop-only (
sm+); mobile stays clean. A "Signed in as โฆ" greeting fires once per real sign-in, never on browser reopen. - SEO โ per-route metadata, Open Graph tags, canonical URLs, and JSON-LD (
Article/ProfilePage/ItemList) on detail and index pages.
- GitHub OAuth login (Auth.js v5) โ no passwords stored anywhere.
- Optional single-admin allowlist via
ADMIN_EMAIL. - Dashboard with content stats and quick actions.
- Full CRUD for posts and projects: create, edit, publish/unpublish, feature/unfeature, archive, delete.
- Publish-date control โ every post/project form has a date picker plus a Today button; pick an older date to back-date the entry (its
publishedAtand creation date move, so lists and sorting treat it as published that day). Leaving it blank keeps the existing date / stamps "now" on publish. Future dates are blocked at the picker, clamped while typing, and rejected by the API schema. - Editable URL slug โ a Slug field under the title on both forms: leave it alone to auto-generate from the title, or type a short one (
/articles/bleve-search). The field previews the live URL and warns that renaming breaks previously shared links. - Never lose a draft โ admin forms autosave to
localStorageevery ~0.7 s. Clicking away with unsaved edits raises an Unsaved changes dialog: Save draft & leave first persists a real draft in the CMS (posts โdraft, projects โarchived/private) and then navigates. Returning to the form restores the local copy with a toast; a successful save/publish clears it. - About page editor โ bio, journey milestones, and skills, all managed from
/admin/about. - Comment moderation (
/admin/comments) โ Posts / Projects filter tabs, threads grouped by source entry, reply / one-time like / inline edit / cascade delete, with counts that refresh live after every mutation (see Comments System). - Markdown editor with live client-side preview tab.
- Status workflow: posts
draft โ published โ archived; projectsactive โ shipped, witharchivedas the private "shelved" state (hidden from the public site, like post drafts). - Fully responsive โ mobile-optimised lists, forms, and an icon-only admin nav.
- Protected by Edge middleware โ unauthenticated visitors are redirected to
/login.
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router, React Server Components) |
| UI | React 19, Tailwind CSS v4 (@theme design tokens) |
| Language | TypeScript 5 (strict) |
| Database | MongoDB via Mongoose 8 (content) + mongodb driver (Auth.js adapter) |
| Auth | Auth.js v5 (next-auth beta) with GitHub provider + MongoDB adapter, JWT sessions |
| Content | MDX (next-mdx-remote/rsc) with remark-gfm, rehype-pretty-code (Shiki) |
| Validation | Zod 3 (API + admin form payloads) |
| Tooling | ESLint 9 (eslint-config-next), tsx for seeding |
- Node.js โฅ 20 (LTS recommended) and npm
- MongoDB โ either a local server (
mongodb://127.0.0.1:27017) or a free MongoDB Atlas cluster - GitHub account โ for the OAuth app that powers admin login
- A Git client (optional, for cloning)
git clone https://github.com/RajHarsh03/BuildLog.git
cd BuildLogStarting from an un-versioned folder instead? Initialize and push it first:
git init git add . git commit -m "BuildLog initial commit" git branch -M main git remote add origin <your-repo-url> git push -u origin main
npm install# Linux / macOS
cp .env.example .env.local
# Windows (PowerShell)
Copy-Item .env.example .env.localThen edit .env.local โ see Environment Variables below.
The minimum for browsing the public site is MONGODB_URI; the admin CMS additionally needs the four AUTH_* values.
npm run seedLoads 11 sample articles and 6 projects (rich MDX content with code blocks and tables).
posts and projects collections before inserting, so re-running it resets all content to the sample data.
npm run devOpen http://localhost:3000 โ the public site. The admin CMS lives at http://localhost:3000/admin.
All variables are documented in .env.example.
| Variable | Required | Description |
|---|---|---|
MONGODB_URI |
Yes (for content) | MongoDB connection string. Local: mongodb://127.0.0.1:27017/buildlog ยท Atlas: mongodb+srv://โฆ |
AUTH_SECRET |
Yes (for admin) | Long random string used to sign JWT sessions. Generate: openssl rand -base64 32, or on PowerShell: [Convert]::ToBase64String([System.Security.RandomNumberGenerator]::GetBytes(32)) |
AUTH_GITHUB_ID |
Yes (for admin) | Client ID of your GitHub OAuth App |
AUTH_GITHUB_SECRET |
Yes (for admin) | Client secret of your GitHub OAuth App |
ADMIN_EMAIL |
No | If set, only this email (your GitHub account email) may access /admin and the mutating API routes. Leave blank to allow any authenticated GitHub user |
NEXT_PUBLIC_SITE_URL |
No | Canonical site origin used in metadata/JSON-LD. Default http://localhost:3000 |
NEXT_PUBLIC_AUTHOR_NAME |
No | Author byline used by seed content and article pages |
NEXT_PUBLIC_AUTHOR_HANDLE |
No | Author handle (e.g. @harshxdotin) |
NEXT_PUBLIC_AUTHOR_WEBSITE |
No | Personal site the handle links to across the UI (default https://harshx.in) |
NEXT_PUBLIC_GITHUB_USERNAME |
No | GitHub account the avatar is auto-synced from (default RajHarsh03) |
NEXT_PUBLIC_AUTHOR_AVATAR |
No | Override avatar URL. Leave blank to auto-fetch from NEXT_PUBLIC_GITHUB_USERNAME (github.com/<user>.png always redirects to the current profile photo) |
Notes:
- In development, a fallback secret is used automatically if
AUTH_SECRETis missing โ always set it in production. - Without
MONGODB_URIthe public pages still render (with empty content states), but admin and API routes cannot function.
- Go to GitHub โ Settings โ Developer settings โ OAuth Apps โ New OAuth App.
- Fill in:
- Application name:
BuildLog(anything) - Homepage URL:
http://localhost:3000 - Authorization callback URL:
http://localhost:3000/api/auth/callback/github
- Application name:
- Click Register application, then Generate a new client secret.
- Copy the Client ID โ
AUTH_GITHUB_IDand the Client secret โAUTH_GITHUB_SECRETin.env.local. - Restart
npm run dev, visit/admin, and sign in with GitHub. - (Recommended) Set
ADMIN_EMAILto your GitHub account's email so only you can administer the site.
For production, register a second OAuth App (or edit the existing one) with your production origin, e.g. https://your-domain.com/api/auth/callback/github.
Install MongoDB Community Server, ensure the service is running, then use:
MONGODB_URI=mongodb://127.0.0.1:27017/buildlog- Create a free cluster at mongodb.com/cloud/atlas.
- Create a database user and copy the connection string.
- Under Network Access, allow your IP (or
0.0.0.0/0for development convenience). - Paste the string into
MONGODB_URI, including the database name, e.g.:
MONGODB_URI=mongodb+srv://<user>:<password>@cluster0.xxxxx.mongodb.net/buildlogAuth.js collections (users, accounts, sessions, verification_tokens) are created automatically on first sign-in. Content lives in posts, projects, about, and comments, all created on first write.
| Command | Description |
|---|---|
npm run dev |
Start the Next.js dev server with Turbopack at http://localhost:3000 |
npm run build |
Production build (type-safe routes, static/dynamic analysis) |
npm run start |
Serve the production build (run build first) |
npm run lint |
ESLint (eslint-config-next) |
npm run typecheck |
tsc --noEmit strict type checking |
npm run seed |
Wipe + load sample posts/projects into MongoDB (see warning above) |
BuildLog/
โโโ app/
โ โโโ (site)/ # Public site (shared header/footer layout)
โ โ โโโ page.tsx # Home
โ โ โโโ about/ # About / bio
โ โ โโโ articles/ # Article index (filters + pagination)
โ โ โ โโโ [slug]/ # Article detail (MDX + TOC + JSON-LD)
โ โ โโโ projects/ # Project browser
โ โ โ โโโ [slug]/ # Project case study
โ โ โโโ not-found.tsx # Public 404 within site chrome
โ โโโ (auth)/
โ โ โโโ login/ # GitHub OAuth sign-in page
โ โโโ admin/ # CMS (middleware-protected)
โ โ โโโ layout.tsx # Server-side auth guard + admin nav
โ โ โโโ page.tsx # Dashboard (stats, recent content)
โ โ โโโ posts/ # List / new / [id]/edit
โ โ โโโ projects/ # List / new / [id]/edit
โ โ โโโ about/ # About-page editor
โ โ โโโ comments/ # Comment moderation
โ โโโ api/
โ โ โโโ auth/[...nextauth]/ # Auth.js endpoint
โ โ โโโ posts/ [id]/view/ # REST CRUD (Zod-validated, admin-only) + view counter
โ โ โโโ projects/ [id]/ # REST CRUD (Zod-validated, admin-only) + view counter
โ โ โโโ about/ # About document (GET/PUT)
โ โ โโโ search/ # Command-palette search (public)
โ โ โโโ comments/ [id]/ like/ # Comments REST (public read, gated write, admin mod)
โ โโโ layout.tsx # Root layout (fonts, global metadata)
โ โโโ globals.css # Tailwind v4 @theme tokens + MDX styles
โ โโโ not-found.tsx # Root 404
โโโ components/
โ โโโ ui/ # Button, Badge, StatusBadge (CVA variants)
โ โโโ layout/ # SiteHeader, SiteFooter, NavLinks, SiteSearch
โ โโโ blog/ # ArticleCard, CodeBlock, TableOfContents, Comments, โฆ
โ โโโ projects/ # ProjectCard, ProjectsBrowser
โ โโโ home/ # CurrentlyBuilding, TypingStatus
โ โโโ shared/ # Icon, Breadcrumbs, ThemeToggle, BackToTop
โ โโโ admin/ # Forms, row actions, nav, markdown preview, SelectDropdown,
โ # AdminComments, LoginError, useFormAutosave (draft safety-net)
โโโ lib/
โ โโโ auth/ # auth.ts, auth.config.ts (edge-safe), guard.ts
โ โโโ client/ # formGuard.ts (unsaved-changes flag + DB draft-saver registry)
โ โโโ content/ # Query + CRUD helpers (posts, projects, about, comments, featured)
โ โโโ db/ # mongodb.ts (Mongoose), mongo-client.ts (driver), models/
โ โโโ mdx/render.ts # compileMDX + TOC plugin + Shiki config
โ โโโ utils.ts # cn(), slugify(), prettifySlug(), formatDate(), readingTimeโฆ
โโโ public/ # Static assets (wordmark logo)
โโโ scripts/seed.ts # Sample content loader (11 posts, 6 projects)
โโโ schemas/ # Zod payloads (post, project, about)
โโโ types/ # Post/Project/Comment/About types + next-auth augmentation
โโโ middleware.ts # Edge auth guard for /admin/*
โโโ .vscode/ # Tailwind v4 lint suppression + extension recommendations
โโโ DESIGN.md # Neo-brutalist design specification
โโโ .env.example # Documented environment template
| Route | Purpose |
|---|---|
/ |
Home: featured article, latest posts, featured-project spotlight |
/about |
Author bio, timeline, toolbelt |
/articles |
Article index with category filter, search, pagination, and a recently-viewed strip |
/articles/[slug] |
Article detail: MDX body, TOC, share, view tracking, comments, related posts |
/projects |
Project browser with status/tech filters (?tech= deep links) |
/projects/[slug] |
Project case study: MDX narrative, TOC rail, author card, comments |
| Route | Purpose |
|---|---|
/login |
GitHub OAuth sign-in (terminal-styled card) |
/admin |
Dashboard: stats, recent posts, quick actions |
/admin/posts, /admin/posts/new, /admin/posts/[id]/edit |
Post management |
/admin/projects, /admin/projects/new, /admin/projects/[id]/edit |
Project management |
/admin/about |
About-page editor (bio, milestones, skills) |
/admin/comments |
Comment moderation (Posts/Projects tabs; threads grouped per post or project) |
| Route | Methods |
|---|---|
/api/auth/[...nextauth] |
Auth.js handshake endpoints |
/api/posts |
GET (all, any status) ยท POST (create) |
/api/posts/[id] |
GET ยท PATCH (partial update) ยท DELETE |
/api/projects |
GET ยท POST |
/api/projects/[id] |
GET ยท PATCH ยท DELETE |
/api/about |
GET ยท PUT (single-document about page) |
/api/comments/[id] |
DELETE (cascade to replies) ยท PATCH (edit body) โ admin only |
/api/comments/like |
POST โ one-way like, admin only |
| Route | Methods |
|---|---|
/api/posts/[id]/view |
POST โ increments a post's view count (public; the client de-dupes with a 30-minute cooldown) |
/api/projects/[id]/view |
POST โ increments a project case study's view count (same public cooldown pattern as posts) |
/api/search?q= |
GET โ returns up to 5 published posts + 5 projects matching the query (powers the command palette; no auth) |
/api/comments?postId= |
GET (threaded comments for an article or project; no auth) ยท POST (comment/reply โ requires GitHub session; site owner is exempt from the cooldown) |
Unauthenticated calls to the admin-only API routes receive 401 Unauthorized; invalid payloads receive 400 with Zod issues.
- Start the app with valid
MONGODB_URI+AUTH_*variables. - Visit
/adminโ you are redirected to/loginโ click Continue with GitHub. - On first sign-in, Auth.js creates your user document in MongoDB and issues a JWT session (stored in an http-only cookie).
- Posts: title, slug (editable; auto-derived from the title if left blank), excerpt, category, tags, cover image, status (
draft/published/archived), featured flag, publish date (back-date + Today button), SEO title/description, and the MDX body. The Preview tab renders the markdown client-side as you type. - Projects: name, slug, description, technologies, image, GitHub/live URLs, status (
active/shipped/archived), featured flag, publish date, MDX case-study body. The row actions include an archive button that hides the project from every public page. - About: bio text, journey milestones (year/title/description), and the skills grid โ edited as a single document.
- Comments: switch between the Posts and Projects tabs, expand an entry's accordion to see its threads (newest activity first); like once, reply once, edit your replies via the โฎ menu, or trash a whole thread. Every mutation toasts and updates the counts instantly.
- Row actions on the list pages let you publish/unpublish, feature, and delete (with two-step confirm) without opening the editor.
- Sign out from the admin top bar โ you land back on the public home, not the login screen.
Draft-safety is always on while editing: forms autosave locally, and leaving an unsaved form through the admin nav offers to persist a real draft to the CMS before navigating away.
Only published posts appear on the public site; drafts and archived posts stay inside the CMS. Likewise, archived projects are excluded from the browser, search, featured spotlight, prerender, and their detail URLs 404.
Article and project bodies are stored as MDX strings in MongoDB and compiled at request time by lib/mdx/render.ts:
remark-gfmโ tables, task lists, strikethrough.rehype-pretty-code(Shiki, dualgithub-light/github-darkthemes, transparent background) โ tokenized code blocks styled by the neo-brutalist.code-blockterminal chrome with a copy button. The correct theme swaps automatically via CSS when dark mode is active, with no re-render.- A custom rehype plugin assigns heading ids with the app's own
slugifyand collects the table of contents in the same pass, guaranteeing TOC anchors always match rendered headings. The TOC renders its own01/02index and strips any manual leading numbering (1.,2)) from the displayed heading text โ ids keep the full text, so anchors never break. - Tables are wrapped in a horizontally scrollable container by the same pipeline, so a wide table (or a reader who bumps the font size up) scrolls inside its card instead of bursting out of the viewport.
Supported markdown in the editor: headings, paragraphs, GFM tables, fenced code with language tags, blockquotes, ordered/unordered lists, links, images, bold/italic/inline code.
Inline images: drop a  line anywhere in the body (or a plain <img> for size/alignment control). Any hosted URL works โ e.g. ImageKit โ and images automatically get the ink-border + hard-shadow treatment at the prose width.
A MongoDB-backed comment engine โ no third-party widgets. Comments live in the comments collection (postId, parentId, body, author snapshot, likedBy/likedByNames) and are exposed through /api/comments*.
- GitHub gate โ signed-out visitors see a sign-in banner; the composer only renders for authenticated GitHub users.
- Threaded replies with a collapsible Replies toggle per thread.
- The 3 newest threads are shown; older discussion stays in the DB and the moderation queue.
- Admin likes surface as a heart badge on the public comment.
- Reader comments are read-only on the public site; moderation is admin-side.
- Anti-spam: 60-second posting cooldown per user + Zod length validation (2โ2000 chars). The site owner (
ADMIN_EMAIL) is exempt, so replying right after commenting works instantly.
- Posts / Projects tabs โ moderate article and case-study threads separately, each tab showing its live comment count.
- Threads grouped per post, sorted by latest activity; posts with no activity for 7 days collapse under an
Older (N)toggle. - Like โ admin-only, one-time, permanent (no unlike endpoint).
- Reply โ one reply per comment.
- Edit / Delete โ replies support inline edit and delete; deleting a root comment cascade-deletes the whole thread.
- Author identity (GitHub login + avatar) is captured at sign-in via the JWT
profile.loginclaim, with an avatar-URL fallback for pre-existing sessions.
The visual language is neo-brutalism, specified in DESIGN.md and tokenized as Tailwind v4 @theme variables in app/globals.css:
- Ink borders: 2px solid
var(--color-ink)on every surface and control. - Hard shadows: zero-blur offsets (
4px 4px 0px var(--color-ink)); hover lifts elements-2px/-2pxwith a6pxshadow; active presses+4px/+4pxwith no shadow. - Layout shell: public pages are full-bleed โ a single
px-5 sm:px-8wrapper (20px mobile / 32px desktop edge gutters) with no forced centered column, matching the prototype. Article/project prose keeps a left-aligned 720px reading measure beside the TOC rail; the home view caps its inner content atmax-w-7xl. The admin CMS body stays centered (max-w-7xl) for table readability. - Palette: warm paper surfaces with a vermilion primary (
#AB2F00) plus Material-style semantic tokens (surface,on-surface,primary,secondary-fixed-dim, โฆ). - Dark mode: a
.darkblock overrides ~50 of those@themetokens (surfaces, ink, badges flip), so every token-driven utility โ and the ink shadows/borders above โ inverts automatically. Boundaries that are meant to stay dark (the hero terminal) use literal colors instead of tokens. - Typography: Newsreader serif for display headings and article prose, Plus Jakarta Sans for UI/body, JetBrains Mono for code and mono labels, plus Material Symbols Outlined icons โ all self-hosted via
next/font(seeapp/layout.tsx). - Toasts โ one shared provider (
components/shared/Toast.tsx) renders a bottom-right stack: ink border,4px 4pxhard shadow, mono text, slide-up entrance, 2.8s auto-dismiss. Hidden belowsm(640px) โ mobile shows no toasts at all.
All components consume these tokens โ never hardcode hex values in components (the always-dark terminal chrome is the one deliberate exception).
npm run build # compiles, type-checks routes, prerenders static pages
npm run start # serves the production build on PORT (default 3000)Production checklist:
-
AUTH_SECRETset to a strong random value (no dev fallback in production). -
MONGODB_URIpoints at Atlas (or a reachable managed instance). -
NEXT_PUBLIC_SITE_URLset to the public origin (used for canonicals/OG/JSON-LD). - GitHub OAuth callback registered for the production origin.
-
ADMIN_EMAILset to lock the CMS to your account.
- Push the repository to GitHub and import it in Vercel (framework preset: Next.js).
- Add all environment variables from
.env.examplein Project โ Settings โ Environment Variables (setNEXT_PUBLIC_SITE_URLto the Vercel domain). - In MongoDB Atlas Network Access, allow the Vercel egress IPs (or
0.0.0.0/0). - Deploy. Run the seed once against Atlas from your machine (
npm run seedwith the Atlas URI in.env.local) or via a temporary API task. - Register the Vercel domain as the GitHub OAuth callback URL.
The Edge middleware and Auth.js config already set trustHost, so no extra proxy configuration is needed on Vercel.
| Symptom | Cause & fix |
|---|---|
IDE shows Cannot find module '@/โฆ' but npm run typecheck/build pass |
Stale TypeScript language server. Command Palette โ TypeScript: Restart TS Server (or reload the window). The code is fine โ don't change imports. |
| Browser still shows the old layout/classes after an edit | Verify what the server actually sends: look for the class string in (Invoke-WebRequest http://localhost:3000).Content. If it's old, make sure the file was saved to disk (editor buffers can differ from disk), run only one dev server per project (two servers sharing .next cause 2.pack.gz_ ENOENT cache warnings), and if it's still stuck stop the server, delete .next, and restart. |
Unknown at rule @theme warning in globals.css |
VS Code's built-in CSS validator doesn't know Tailwind v4 at-rules. Harmless; already silenced via .vscode/settings.json. Install the Tailwind CSS IntelliSense extension for full support. |
| Public pages render but show no content | MONGODB_URI missing/unreachable, or DB not seeded. Fix the URI and run npm run seed. |
/admin redirects to /login in a loop after signing in |
Missing/invalid AUTH_SECRET, or callback URL mismatch in the GitHub OAuth App. Check both, then restart the dev server. |
Sign-in works but /admin still denies access |
ADMIN_EMAIL is set to a different email than your GitHub account's. Update or clear it. |
| Article page shows "join the discussion" instead of the comment box | Expected โ comments require a GitHub session (same OAuth as the CMS). Sign in from the banner; the composer appears after the session resolves. |
POST /api/comments returns 429 |
The 60-second per-user posting cooldown is active. Wait a minute and retry. |
npm run seed removed my articles |
Expected: the seed wipes posts/projects first. Re-create content via the CMS, or don't re-run the seed against real data. |
Build fails on CSS minification with Parser.attribute error |
A substring attribute selector (e.g. code[data-theme*=" "]) crashes Next's cssnano. Avoid *= selectors on attributes containing spaces in globals.css. |
Private personal project โ all rights reserved.
ยฉ 2026 Harsh ยท Back to top โ