CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Jekyll 4 static blog deployed to GitHub Pages at sameerbajaj.com. Features blog posts, book notes, and a tools/projects showcase. SEO and organic search are primary traffic drivers — prioritize proper metadata, performance, and discoverability.
Critical Workflow Rules
- Never start the Jekyll dev server. The user runs it themselves. If you need them to check changes in a browser, ask them to review (they’re usually already running it).
- JS files must be updated in pairs. Each
assets/js/*.jsfile has a hand-maintained*.min.jscounterpart used in production. When changing JS, update both files. They are not auto-generated.
Common Commands
# Local development (user runs this, not you)
bundle exec jekyll serve --livereload --config _config.yml,_config-dev.yml
# Faster dev modes via npm (see DEVELOPMENT.md for details)
npm run dev:full # Full fidelity
npm run dev:fast-design # CSS/layout work (limit_posts: 10)
npm run dev:fast-content # Writing only (fastest, skips tools/books pages)
# Production build
bundle exec jekyll build
# Clean build cache
bundle exec jekyll clean
# HTML validation
bundle exec rake # Runs HTMLProofer on _site/
# Image optimization
uv run scripts/optimize-images.py [--dry-run] [--force]
uv run scripts/optimize-images.py --responsive [--sizes 400,800,1200]
# Search engine indexing
npm run submit:all # Submit URLs to Google + Bing
npm run submit:google
npm run submit:bing
Ruby 3.2.2 via rbenv. Always use rbenv Ruby, not system Ruby.
Architecture
Layout inheritance chain
compress.html → default.html → page.html → post.html
→ book_notes.html
→ other_notes.html
→ posts_all.html
default.html is the main shell — it owns <head>, JSON-LD structured data, Open Graph/Twitter meta tags, dark mode init script, and critical CSS inlining. Everything ultimately wraps in compress.html for HTML minification.
Content types
- Blog posts —
_posts/YYYY-MM-DD-slug.mdwithlayout: post - Book notes —
_posts/YYYY-MM-DD-slug.mdwithlayout: book_notes(adds rating, author, date_read) - Other notes —
layout: other_notes(talks, videos, misc) - Tools —
_data/tools.ymldrivestools.htmland individual pages intools/ - Fiction books —
_data/fiction_books.yml(structured YAML, no individual pages)
Config layering
Configs are layered via --config flag. Later files override earlier ones:
_config.yml— Production (full minification, all plugins, analytics)_config-dev.yml— Dev overrides (incremental builds, no minification, no plugins)_config-dev-fast-design.yml— Addslimit_posts: 10, keeps CSS compilation_config-dev-fast-content.yml— Addslimit_posts: 10, excludes CSS/tools/books for fastest builds
Environment-gated behavior
Several things change based on jekyll.environment == "production" (set automatically by GitHub Pages):
_includes/scripts.htmlloads*.min.jsin production,*.jsin development_layouts/default.htmlinlines critical CSS in production via `@import “base/variables-css”; @import “base/variables”; @import “base/fontface”; @import “base/normalize”; @import “base/syntax”; @import “base/general”; @import “base/helpers”;
/* Micro perf: text rendering and font synthesis */ body{ text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; font-synthesis-weight: none; font-synthesis-style: none; }
@import “pages/home-blog-projects”; @import “pages/page”; @import “pages/post”;
@import “components/author”; @import “components/disqus”; @import “components/footer”; @import “components/header”; @import “components/nav”; @import “components/others”; @import “components/pagination”; @import “components/related”; @import “components/share”; @import “components/side-by-side”; @import “components/social-links”; @import “components/spoiler”; @import “components/toc”; @import “components/books”; @import “components/newsletter”; @import “components/floating-actions”; @import “components/home-hero”; @import “components/tag-filter”; @import “pages/tools”; `
- Asset URLs get cache-busting
?v=suffix (epoch timestamp in prod,devlocally) toc.jsandfeedback-widget.jsonly load on post/book_notes/other_notes layouts
CSS / Dark mode system
The stylesheet compiles from _includes/style.scss, which imports all SCSS partials. The import order matters — _sass/base/variables-css.sass must come first.
Dark mode uses CSS custom properties defined in _sass/base/variables-css.sass under :root (light) and html.dark-mode (dark). Never hardcode colors — always use var(--background-color), var(--text-color), etc. For cases where CSS variables aren’t sufficient, use .dark-mode .component selectors. See docs/DARK_MODE_GUIDE.md for the full variable reference and patterns.
The dark mode toggle is in assets/js/dark-mode.js with localStorage persistence. A blocking inline script in default.html <head> applies the class before first paint to prevent flash.
Custom font: Signifier (woff2), preloaded in <head>.
Newsletter popup
Controlled by show_newsletter_popup in _config.yml. The flag is emitted as a <meta> tag in default.html and read by newsletter.js via window.siteConfig. This flag only controls the JS modal popup, not the inline post newsletter section (`<div class="post-newsletter" id="postNewsletter" data-cta-location="post_footer">
</div> `).
Front Matter Conventions
Blog posts:
---
title: "Post Title"
layout: post
date: YYYY-MM-DD HH:MM
last_modified_at: YYYY-MM-DD HH:MM # Bump when substantially revising (signals freshness to Google)
image: /assets/images/filename.webp # OG image; fallback: image_url → first <img> in content → site avatar
description: "SEO description (used for meta tags, ~150-160 chars)"
excerpt: "Preview text shown in post listings"
tags: [tag1, tag2]
category: blog
author: sameerbajaj
---
Book notes add: book_title, book_author, book_url, book_thoughts, rating (out of 10), date_read, star (featured flag).
Tools (_data/tools.yml): title, description, url, category, launch_date, pinned, image.
Image Handling
- Images in
assets/images/(tool icons inassets/images/tools/) - Prefer WebP; originals backed up to
assets/images/_originals/ optimize-images.pytracks state in.image-manifest.json- Max 1600px width (2x retina); use
srcset/sizesfor responsive images - OG images: 1200×630px recommended
CI/CD
GitHub Actions workflows in .github/workflows/:
submit-to-search-engines.yml— Auto-submits changed URLs to Google + Bing on push to maincheck-and-submit-all.yml— Manual workflow to check indexing status or bulk-submit all URLs
Documentation
Detailed guides in docs/:
DEVELOPMENT.md— Setup (rbenv, Ruby, Jekyll), build modes, troubleshootingWRITING_GUIDE.md— OG images, titles, descriptions, date handlingDARK_MODE_GUIDE.md— CSS variable system, patterns, anti-patternsGA_EVENTS_GUIDE.md— Analytics event tracking reference