Date: 2026-05-19 Scope: Full codebase structural analysis (875 source files, ~51K lines) Goal: Identify structural issues blocking the August lean-codebase target
The site is a well-built Next.js 16 app with solid fundamentals (strict TypeScript, no circular deps, consistent imports, proper SQL parameterization). The main structural problems are:
Stats: 79 pages, 27 API routes, 8 route groups
| Group | Status |
|---|---|
(info) |
Completely empty |
(media) |
Completely empty |
(updates) |
Has layout.tsx + CSS but zero routes |
The tracking section has 5 near-identical page patterns:
(tracking)/anime/ + anime/watched/(tracking)/film/ + film/watched/(tracking)/tv/ + tv/watched/(tracking)/games/ + games/played/(tracking)/manga/ + manga/read/Each has its own layout.tsx (12 lines, identical structure), its own API route, its own client component. The pages differ only in data source and field names. This is the single biggest structural redundancy.
Fix: Generic (tracking)/[medium]/ dynamic route + shared layout + consolidated API at /api/tracking/[medium]/.
/categories (nav group) vs /(content)/[type]/categories (per-content-type)/tags (nav group) vs /(content)/[type]/tagssequences/categories/page.tsx (58 lines) vs sequences/category/page.tsx (26 lines, empty placeholder)| File | Lines | Issue |
|---|---|---|
api/system/utils/route.ts |
762 | Kitchen-sink endpoint, needs splitting |
(misc)/stats/client.tsx |
729 | Heavy viz logic mixed with UI |
(misc)/colophon/client.tsx |
520 | Could extract sections |
(misc)/guestbook/client.tsx |
474 | Form + display + interactions |
api/tracking/reading/route.ts |
449 | Complex aggregation |
(content)/sequences/client.tsx |
387 | Sequence browser |
(nav)/search/client.tsx |
351 | Search interface |
(misc)/contact/client.tsx |
347 | Contact form |
(tracking)/globe/client.tsx |
345 | Map viz |
api/reference/route.ts |
318 | Reference data |
(misc)/symbols/client.tsx |
305 | Symbol display |
These are copy-paste identical (import CSS, wrap in <div className="py-8">):
(tracking)/anime/layout.tsx(tracking)/film/layout.tsx(tracking)/tv/layout.tsx(tracking)/manga/layout.tsx(tracking)/games/layout.tsx(tracking)/globe/layout.tsx(tracking)/reading/layout.tsxFix: Single (tracking)/layout.tsx with shared styles.
Tracking API routes follow identical patterns:
/api/tracking/anime/route.ts (166 lines)/api/tracking/film/route.ts (150 lines)/api/tracking/tv/route.ts (187 lines)/api/tracking/manga/route.ts (169 lines)/api/tracking/media/route.ts (146 lines)All query media-db.ts with slightly different table names. Could be one parameterized route.
Stats: 152 files, 23,057 lines, 25 directories
| File | Lines | What's Inside |
|---|---|---|
media/reading/reading.tsx |
1,589 | 9+ distinct sub-components, data fetching, state, rendering |
interactive/survey.tsx |
1,220 | Form state, validation, conditional rendering, answer tracking |
content/404-block.tsx |
756 | Multiple error page patterns |
content/sequence.tsx |
686 | Parsing + rendering + styling + fetch |
seo/print.tsx |
602 | Print layout + styling logic |
content/verse.tsx |
584 | Verse display + formatting |
interactive/popups.tsx |
531 | Modal manager |
These components mix presentation with data fetching -- should accept data as props or use server components:
media/reading/reading.tsx - fetches reading datacontent/sequence.tsx - fetches code sequencescontent/404-block.tsx - fetches error datacontent/feed-shared.tsx - fetches feed updatescontent/scripts.tsx - fetches script contentcontent/graph.tsx - fetches graph datainteractive/popups.tsx - fetches popup datalayout/header.tsx - fetches header contentlayout/footer.tsx - fetches footer datahome/about/InterestingPeople.tsx - fetches people datahome/HomeGitHubContributions.tsx - GitHub API callshome/ListHeader.tsx - fetches quote datanav/search-overlay.tsx - search API callsnav/settings-panel.tsx - loads settingspages/quotes/infiniteMovingQuotes.tsx - fetches quotespages/quotes/quoteOfTheDay.tsx - fetches daily quotehome/about/WordOfTheDay.tsx - fetches daily wordhome/about/Favorites.tsx - loads favoritestypography/1611bible.tsx - fetches bible data10 card variants:
ui/card.tsx, ui/glowCard.tsx, ui/glowCardGrid.tsx, ui/hover-card.tsxcontent/PaginatedCardGrid.tsx, content/mediaCard.tsxpages/books/book-card.tsx, pages/predictions/prediction-card.tsxtypography/quoteCard.tsx, home/PoetryCard.tsx3 header variants:
layout/header.tsx (432 lines)layout/oc-header.tsx (224 lines)home/HomeHeader.tsx (114 lines)3 footer variants:
layout/footer.tsx (233 lines)layout/global-footer.tsx (92 lines)typography/expanded-footer-block.tsx (162 lines)4 quote components:
pages/quotes/quoteOfTheDay.tsxpages/quotes/quotesFeed.tsxpages/quotes/infiniteMovingQuotes.tsxtypography/quoteCard.tsx2 duplicate named files:
typography/footnotes.tsx (58 lines) vs content/footnotes.tsx (141 lines)typography/table.tsx vs content/table.tsx (284 lines)Mixed PascalCase and kebab-case within same directories:
home/ - featured-post.tsx alongside HomeHeader.tsxhome/about/ - name-breakdown.tsx alongside InterestingPeople.tsxnav/ - search-overlay.tsx alongside NeighborsOverlay.tsxConvention from CLAUDE.md says camelCase for component filenames, but neither PascalCase nor kebab-case matches that.
contribution-graph.tsx sits at components root, belongs in home/rolls/ directory has a single file (326 lines), belongs in interactive/ or pages/changelog/ directory exists but is emptymedia/anime/ directory exists but may be emptyStats: 38 files in lib/, 7,468 lines total
date.ts (10 dead):
formatRelative, formatInCentralTime, getCurrentMonthYear, getCurrentYearisPast, isFuture, isToday, formatDateCompact, formatDateWithValidation, isValidDatelab-db.ts (6 dead):
getSurveyResponses, getAllSurveyResponses, getSurveyResponseCountgetAllSurveys, upsertSurvey, syncSurveysFromFilesdoc.ts (3 dead):
getDocumentsUnderPath, getDocBySlug, getTopLevelDirectoriesdata.ts (2 dead):
getVerseByTypeAndSlug, getDocumentFilePathmdx.ts (3 dead):
fileExists, listMdxFiles, extractHeadingsWithHierarchyOther (7 dead):
analytics-db.ts: getHistoryTimelinecanonical-url.ts: _resetCanonicalUrlCachecookie-sign.ts: signCookieform-parser.ts: extractFormFrontmatterguestbook-db.ts: getGuestbookEntrymedia-db.ts: getMusicPlaylistsprompts.ts: getPromptsByCategory, getPromptBySluggetMovies() defined twice:
media-db.ts:getMovies() - direct SQLite queryfilm-utils.ts:getMovies() - async wrapper that calls the above + transforms field namesTilEntry/NowEntry defined twice:
data.ts (without id field)system-db.ts (with id: number)Date formatting redundancy:
formatCompact vs formatDateCompact, formatISO vs formatYMD, formatRange vs formatDateRange)| Pattern | Tech | Files | Issue |
|---|---|---|---|
withDb() wrapper |
SQLite | 108 uses | Good, canonical |
| Neon serverless | Postgres | analytics-db.ts only | Different paradigm, async vs sync |
| Manual singleton | SQLite | reference-db.ts only | Reinvents withDb(), connection never closed |
Fix: Migrate reference-db.ts to use withDb(). Decide analytics future (keep Postgres or migrate to SQLite).
| File | Lines | Exports | Recommendation |
|---|---|---|---|
media-db.ts |
837 | 91 | Split by domain (films, games, anime, reading) |
data.ts |
713 | 36 | OK as-is, well-organized |
system-db.ts |
620 | 30 | Split: metadata, quotes, supporters |
analytics-db.ts |
504 | 30 | OK, cohesive domain |
survey-parser.ts |
345 | - | OK, single responsibility |
seo.ts |
320 | 5 | Split: JSON-LD, feeds, OG |
Type definitions live in 4+ locations:
src/lib/types/content.ts (264 lines)src/lib/types/media.ts (58 lines)analytics-db.ts, media-db.ts, system-db.ts, reference-db.tsNo single source of truth for types.
16MB of fonts in public/fonts/ across 10+ families (53 .woff2 files):
Many are likely unused or used on a single page. Audit needed.
build:lowmem disables both type-checking (NEXT_DISABLE_TYPE_CHECKING=1) and ESLint (DISABLE_ESLINT_PLUGIN=true)Hybrid approach (all acceptable, no conflicts):
globals.css at 48K is large with excessive !important in code block styles| Database | Size | Notes |
|---|---|---|
| reference.db | 59MB | Largest by far, contains poetry/reference data |
| content.db | 8.7MB | Main content |
| system.db | 2.4MB | Metadata, quotes, TIL |
| media.db | 656KB | Films, anime, games |
All intentionally committed to git via !data/*.db gitignore exception.
Cookie secret default (CRITICAL):
cookie-sign.ts:3 has hardcoded fallback: 'krisyotam-cookie-secret-change-in-prod'COOKIE_SECRET not set in productionTikZ XSS risk (MEDIUM):
typography/tikz.tsx renders SVG via dangerouslySetInnerHTML from server APICSRF localhost bypass:
csrf.ts allows localhost:3000 and localhost:3080 without environment checkZero error.tsx files in the entire app router. A crash in any route shows the default Next.js error page with no recovery path.
Only 3 found (minimal debt):
surveys/[slug]/client.tsx:40 - // TODO: Submit to Supabasetypography/tweet.css:53,59 - // TODO: figure out a way to reuse this with a.tsx100+ instances of krisyotam.com hardcoded across the codebase. Acceptable for a personal site but blocks any future domain changes.
URL replacement logic (2 files, identical):
typography/citation.tsx:28-30seo/print.tsx:35-36Image unoptimized check (7 files, identical):
unoptimized={image?.includes('krisyotam.com')}40+ console.error calls across production code. Most are legitimate error logging but several in components should be removed or replaced with proper error handling.
| Area | Grade | Key Issue |
|---|---|---|
| Route organization | B- | Empty groups, tracking duplication |
| Component architecture | C+ | 2 monoliths, 19 files with data fetching in UI |
| Lib/data layer | B | 31 dead exports, 3 DB paradigms |
| Type system | B- | Scattered definitions, duplicates |
| Config/build | B | Font bloat, no linting enforcement |
| Security | B- | Cookie fallback, no error boundaries |
| Code hygiene | B+ | Minimal TODOs, consistent imports, no circular deps |
| Overall | B- | Solid foundation, needs consolidation |
(info), (media), (updates))withDb() pattern!important overrideshome/src/components/media/reading/reading.tsx (1,589 lines)
src/components/interactive/survey.tsx (1,220 lines)
src/app/api/system/utils/route.ts (762 lines)
src/app/(tracking)/*/layout.tsx (7 identical files)
src/app/(tracking)/*/page.tsx (5 near-identical patterns)
src/app/api/tracking/*/route.ts (5 near-identical APIs)
src/lib/media-db.ts (837 lines, 91 exports)
src/lib/system-db.ts (620 lines)
src/lib/reference-db.ts (rogue singleton pattern)
src/lib/date.ts (14 formatters, 10 dead)
src/lib/types/ (scattered across 4+ locations)
src/lib/cookie-sign.ts (security fix)
src/components/content/404-block.tsx (756 lines)
src/components/content/sequence.tsx (686 lines)
src/components/seo/print.tsx (602 lines)
src/components/content/verse.tsx (584 lines)
src/components/interactive/popups.tsx (531 lines)
src/app/globals.css (48K, !important abuse)