Imported from whexy/blog-1999 (
AGENTS.md). Install upstream withnpx skills add whexy/blog-1999. Copyright stays with the author.
Agent Guidelines for blog-1999
This document provides coding agents with essential information about the blog-1999 codebase.
Project Overview
Next.js (App Router) blog system with MDX content and Notion CMS integration. Uses TypeScript, Tailwind CSS v4, React 19, and server-side rendering. The toolchain is Nix-managed (Node 22 / pnpm 10).
Build & Development Commands
# Install dependencies (uses pnpm 10)
pnpm install
# Development server (http://localhost:3000)
pnpm run dev
# Production build
pnpm run build
# Start production server
pnpm run start
# Lint all files (ESLint flat config)
pnpm run lint
# Auto-fix lint/formatting issues
pnpm run lint:fix
# Opt-in Tailwind design-system audit (warnings only)
pnpm run lint:tailwind
Nix (Blueprint layout)
The repo is Nix-managed via numtide/blueprint,
with all Nix files under nix/ (prefix configured in flake.nix).
nix develop # reproducible dev shell (Node 22, pnpm 10, TypeScript) + installs git hooks
nix flake check # runs lint + typecheck checks in an offline sandbox
nix fmt # treefmt: prettier (project) + nixfmt (*.nix)
With direnv, .envrc (use flake) auto-loads the dev
shell on cd (run direnv allow once).
nix/devshell.nix- default dev shell; its shellHook installs the git-hooks.nix/formatter.nix+nix/treefmt.nix-nix fmt(shared treefmt config).nix/pre-commit-check.nix- git-hooks.nix config (treefmt, eslint, tsc, nil, statix). Devshell-only (not a flake check), because eslint/tsc/prettier need the project-localnode_modules. Runs on everygit commit; the generated.pre-commit-config.yamlis gitignored.nix/checks/{lint,typecheck}.nix- flake checks (consume the deps package viaperSystem.self.pnpm-deps). These provide the offline/CI lint+typecheck coverage that the working-tree git hooks cannot run in the sandbox.nix/packages/pnpm-deps.nix- offline pnpm dependency store, exposed aspackages.<system>.pnpm-deps(a fixed-output derivation). Whenpnpm-lock.yamlchanges, thehashhere must be regenerated: set it topkgs.lib.fakeHash, run a build, copy thegot:hash.
Testing
- No automated test suite currently configured
- Manual testing via
pnpm run devand browser verification - Type checking via TypeScript compiler:
npx tsc --noEmit - CI-style verification:
nix flake check(lint + typecheck, fully offline)
Code Style & Formatting
Prettier Configuration
- Print Width: 70 characters
- Indentation: 2 spaces, no tabs
- Quotes: Double quotes for strings
- Semicolons: Required
- Trailing Commas: Always (ES5+ compatible)
- Arrow Functions: Avoid parentheses when possible (
x => x) - Bracket Spacing: Enabled (
{ foo }not{foo}) - JSX: Brackets on same line as last prop
- Plugins: prettier-plugin-tailwindcss (auto-sorts classes)
ESLint Rules
- Flat config:
eslint.config.mjs(ESLint 9 flat config; the legacy.eslintrc.jsonhas been removed) - Composes:
@eslint/jsrecommended,eslint-config-next(core-web-vitals + typescript),typescript-eslintrecommended, andeslint-plugin-prettier/recommended - Unused vars: Error
- Explicit any: Error (avoid
anytype) - Prettier integration enabled
- Run directly with
pnpm run lint(eslint .);next lintis removed in Next 16
TypeScript Configuration
Strict Mode
strict: false(permissive mode)strictNullChecks: false- Use optional chaining (
?.) and nullish coalescing (??) liberally
Path Aliases
@/components/* → components/*
@/lib/* → lib/*
@/data/* → data/*
@/public/* → public/*
Target & Module
- Target: ES2017
- Module: esnext, bundler resolution (set by Next 16)
- JSX: react-jsx (React 17+ transform)
Project Structure
app/ - Next.js 13+ App Router pages
(root)/ - Main site pages with root layout
(dyn)/ - Dynamic Notion-based content
components/ - React components
UI/ - User interface components
MDX/ - MDX content components
Scripts/ - Analytics and scripts
Layouts/ - Layout components
Widgets/ - Reusable widgets
lib/ - Utilities and external service integrations
data/ - Static content (MDX blog posts, metadata)
blog/ - MDX blog post files
public/ - Static assets (images, files, etc.)
styles/ - Global CSS, Prism themes, KaTeX styles
Naming Conventions
Files & Directories
- Components: PascalCase (
WelcomeCard.tsx,PostPage.tsx) - Utilities: camelCase (
blog.ts,spotify.ts) - Directories: PascalCase for component folders, lowercase for utility folders
- Route Segments: Next.js conventions (
[lang],(root),layout.tsx,page.tsx)
Code
- React Components: PascalCase, default export
- Functions: camelCase
- Types/Interfaces: PascalCase
- Constants: camelCase (not SCREAMING_SNAKE_CASE)
- Type suffix: Use
typekeyword for aliases,interfacefor object shapes
Import Style
Order (enforced by prettier-plugin-tailwindcss)
- React/Next.js imports
- Third-party packages
- Path alias imports (
@/...) - Relative imports (
./,../) - Asset imports (images, styles)
Example
import React from "react";
import Link from "next/link";
import Image from "next/image";
import { somePackage } from "some-package";
import ComponentName from "@/components/UI/ComponentName";
import { utilFunction } from "@/lib/utils";
import helloPic from "@/public/img/face.png";
Component Patterns
Server Components (default)
- Use async/await for data fetching
- No useState, useEffect, or browser APIs
- Access file system, environment variables directly
Client Components
- Add
"use client"directive at top - Use for interactivity, hooks, browser APIs
Props & Types
interface ComponentProps {
title: string;
description?: string; // optional
children?: React.ReactNode;
}
const Component = ({
title,
description = "default",
}: ComponentProps) => {
// implementation
};
export default Component;
Styling with Tailwind
- Design system first: the project has a documented design language
("Frost") in
DESIGN.md. Semantic classes (glass-card,panel,btn-glass,bubble,segmented,nav-link, ...) and tokens (max-w-content,font-title, ...) live instyles/globals.css. Use them before writing raw utilities; promote patterns repeated 3+ times into@layer components. - Use Tailwind utility classes for one-off layout/spacing
- Custom colors defined:
white-readable,black-readable - Custom fonts:
font-title(Lato),font-article(Fira Sans, then Noto Sans SC),font-mono(JetBrains Mono) - Responsive: mobile-first (
sm:,md:,lg:) - Dark mode: not currently implemented
Error Handling
- Use try/catch for async operations
- Throw errors with descriptive messages
- Return
undefinedornullfor missing data (not errors) - Use optional chaining for potentially undefined values
export function getBlogPost(slug: string): BlogPost | undefined {
const all = getAllBlogPosts();
return all.find(p => p.slug === slug) ?? undefined;
}
Content Management
MDX Blog Posts
- Location:
data/blog/*.mdx - Frontmatter:
title,summary,publishDate,lang(en|zh),series - Filename pattern:
slug.mdxorslug.en.mdx/slug.zh.mdx - Access via
lib/blog.ts:getAllBlogPosts(),getBlogPost(slug, lang)
Notion Integration
- Dynamic content in
app/(dyn)/routes - Uses
react-notion-xfor rendering
Common Gotchas
- No
anytypes: ESLint will error on explicitany - Image optimization: Always use
next/imagefor images - Font loading: Fonts configured in
app/(root)/layout.tsx - Path aliases: Use
@/imports, not relative paths across directories - Caching: Blog posts are parsed once and cached in memory by
lib/blog.ts - Date handling:
publishDateis date-only; pre-2022 posts are Beijing time, later posts Chicago time - pnpm config: Dependency
overrides/onlyBuiltDependencieslive inpnpm-workspace.yaml(pnpm 10), not the deprecatedpnpmfield inpackage.json. - Nix deps hash: After changing
pnpm-lock.yaml, regenerate the hash innix/packages/pnpm-deps.nixornix flake checkwill fail. next-env.d.ts: Tracked on purpose (it provides the image module typestsc --noEmitneeds on a fresh checkout and in the Nix typecheck).next dev/next buildrewrite its routes import; don't commit that churn.
Before Committing
- Run
pnpm run lintto catch issues - Verify TypeScript:
npx tsc --noEmit - Test in browser with
pnpm run dev - Ensure build succeeds:
pnpm run build - Or run all checks at once:
nix flake check
