This document covers all the developer tooling, automation, and quality control systems used in this project.
- Linting & Formatting
- Testing
- Automation & Git Hooks
- Security Auditing
- Bundle Analysis
- Accessibility Testing
- Yarn Plug'n'Play (PnP)
- TypeScript Configuration
- Component Library (shadcn/ui)
ESLint provides static analysis for JavaScript, TypeScript, JSX, and MDX files.
Configuration: eslint.config.mjs
Features:
- Extends
next/core-web-vitalsfor Next.js best practices - TypeScript support via
typescript-eslint - MDX support via
eslint-plugin-mdxwith remark processor - Integrates with Prettier via
eslint-config-prettierto avoid conflicts
Available Commands:
yarn lint # Lint all files (.js, .jsx, .ts, .tsx, .mdx)
yarn lint:mdx # Lint only MDX filesMDX Linting:
- MDX files are parsed and validated for proper JSX/Markdown mixing
- Catches issues like unclosed tags, improper nesting, and HTML/Markdown conflicts
- Common pitfall: avoid mixing
<br />tags inside markdown lists - use blank lines instead - Always add blank lines before/after JSX components within markdown content
Prettier enforces consistent code formatting across the entire codebase.
Configuration: .prettierrc.json
Settings:
- No semicolons (
semi: false) - Single quotes (
singleQuote: true) - 2-space indentation (
tabWidth: 2) - ES5 trailing commas (
trailingComma: "es5") - 80-character line width (
printWidth: 80) - MDX-specific: prose wrapping always enabled (
proseWrap: "always")
Available Commands:
yarn format # Format all project files
yarn format:mdx # Format only MDX filesIntegration:
- Runs automatically on staged files via
lint-staged(pre-commit hook) - ESLint is configured to respect Prettier's formatting rules
Jest provides the testing framework with React Testing Library for component testing.
Configuration: jest.config.js
Setup: jest.setup.js imports @testing-library/jest-dom for enhanced matchers
Available Commands:
yarn test # Run tests in watch modeKey Learnings:
-
Type Definitions:
- Explicitly add
"types": ["jest", "@testing-library/jest-dom"]totsconfig.jsoncompilerOptions - This ensures TypeScript recognizes Jest matchers like
toBeInTheDocument() - Restart your IDE/TypeScript server after configuration changes
- Explicitly add
-
Mocking
Math.random()in Components:- When testing components that use
Math.random()inuseStateinitializers, mockglobal.Math.randominbeforeEach() - Recommended strategy: capture initial state → simulate interaction → capture new state → assert they differ
- Use simple mocks (e.g., return 0.1, then 0.8) to ensure different values
- When testing components that use
-
ESLint and
jest.config.js:jest.config.jsusesrequire()for Next.js compatibility- Add
// eslint-disable-next-line @typescript-eslint/no-require-importsaboverequire()lines if needed
-
Integration with
lint-staged:- Tests run automatically on staged files during pre-commit
- Uses
--bail --findRelatedTests --passWithNoTestsflags for efficient CI-like behavior
Testing Best Practices:
- Write tests alongside features, not after
- Test user behavior, not implementation details
- Use semantic queries (
getByRole,getByLabelText) over test IDs - Clean up mocks in
afterEach()withjest.restoreAllMocks()
Husky manages Git hooks to enforce quality checks before commits.
Configuration: .husky/pre-commit
Note: The pre-commit hook is currently commented out for Windows development compatibility. Uncomment for Linux/macOS:
# .husky/pre-commit
yarn lint-stagedAutomatically runs quality checks on staged files before allowing a commit.
Configuration: package.json → lint-staged field
Current Setup:
{
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --fix",
"jest --bail --findRelatedTests --passWithNoTests"
],
"*.mdx": ["prettier --write", "eslint --fix"]
}What It Does:
- Format files with Prettier
- Lint and auto-fix issues with ESLint
- Test related test files with Jest (JS/TS files only)
- Validate MDX syntax (MDX files)
Benefits:
- Catches issues before they reach CI
- Ensures consistent code style across the team
- Prevents committing broken tests
- Zero configuration needed for contributors
A security audit runs automatically before every git push:
Configuration:
.husky/pre-push- macOS / Linux / Git Bash.husky/pre-push.bat- Windows CMD / PowerShell
Behavior:
- Runs
yarn npm audit --severity critical - Blocks push if critical vulnerabilities exist
- Bypass with
git push --no-verify(emergencies only)
Security auditing runs at multiple layers to catch vulnerabilities early:
| Layer | When | Purpose |
|---|---|---|
| Pre-push hook | Before git push |
Local feedback |
| GitHub Actions (PR) | Every pull request | Gate vulnerable code |
| GitHub Actions (Cron) | Weekly (Sunday 2 AM UTC) | Catch newly disclosed vulns |
| Dependabot | Continuous | Auto-create fix PRs |
yarn audit # Full vulnerability report
yarn audit:critical # Critical severity only (used by CI)
yarn audit:fix # Attempt automatic fixesConfiguration: .github/workflows/security-audit.yml
Triggers:
- Pull requests to
main/master - Pushes to
main/master - Weekly cron schedule (catches newly disclosed vulnerabilities)
- Manual dispatch from GitHub UI
On failure: Creates a GitHub issue (scheduled runs only) to alert maintainers.
Configuration: .github/dependabot.yml
Features:
- Weekly dependency updates (Monday 9 AM ET)
- Groups patch updates to reduce PR noise
- Separate tracking for GitHub Actions dependencies
- Auto-labels PRs with
dependenciestag
- Check severity:
yarn audit - Update affected package:
- Direct dependency: Update version in
package.json, runyarn install - Transitive dependency: Add to
resolutionsfield inpackage.json:This forces all packages to use the patched version, even if they request older versions.{ "resolutions": { "vulnerable-package": "^patched.version" } }
- Direct dependency: Update version in
- Verify fix:
yarn audit:critical(should show no suggestions) - Push: Pre-push hook confirms fix before code leaves your machine
Note: Dependabot alerts show the dependency chain (e.g., tailwindcss → ... → glob 10.4.5). Use resolutions when upstream packages haven't updated yet.
Visualizes the size and composition of your production bundles.
Configuration: next.config.js with @next/bundle-analyzer
Command:
yarn analyzeOutputs:
.next/analyze/nodejs.html- Server-side bundle analysis.next/analyze/edge.html- Edge runtime bundle analysis
🟢 Good Signs:
- Balanced rectangle sizes (no single massive dependencies)
- Clear separation between vendor code and application code
- Efficient code splitting across routes
🟡 Monitor These:
- date-fns: Ensure tree-shaking with specific imports:
import { format } from 'date-fns' - @heroicons/react: Should only include used icons
- MDX processing libraries: Necessary but watch for bloat
🔴 Red Flags:
- Duplicate dependencies across bundles
- Disproportionately large rectangles
- Unused code from large libraries
- Import Optimization: Use specific imports instead of entire libraries
- Dynamic Imports: Use
React.lazy()ornext/dynamicfor code splitting - Image Optimization: Leverage Next.js
Imagecomponent - Dependency Audit: Regular review of bundle impact before adding new dependencies
- Tree Shaking: Verify webpack is eliminating unused code
- ✅ Lean Runtime Dependencies: Well-curated dependency list
- ✅ Modern Stack: React 19 + Next.js 15 optimizations
- ✅ Tree-Shakable Libraries: Most dependencies support tree-shaking
⚠️ Monitor: MDX stack and date-fns usage patterns
Combined with Vercel Analytics and Speed Insights, use bundle analysis to:
- Identify bottlenecks before they impact users
- Track bundle size over time as features are added
- Optimize critical paths for better Core Web Vitals
Three complementary tools ensure WCAG 2.1 AA compliance:
- Type: Static analysis during development
- What it catches: Missing alt text, invalid ARIA attributes, non-semantic HTML
- When it runs: During
yarn lintand pre-commit hooks
- Type: Runtime WCAG testing
- What it catches: Contrast issues, focus management, live region problems
- Command:
yarn access(starts dev server → runs audits → saves reports)
- Type: Comprehensive accessibility audits
- What it catches: Performance impact on accessibility, best practices, SEO
- Runs on: Home page, demos page, resources page
Full Suite:
yarn accessThis command:
- Starts the development server at
http://localhost:3000 - Runs ESLint for static analysis
- Runs Axe-core for WCAG checks (tags:
wcag2aa) - Runs Lighthouse audits on key pages
- Saves reports to
./accessibility-reports/
Review Reports:
accessibility-reports/axe-report.json- Axe-core findingsaccessibility-reports/lighthouse-report-*.html- Lighthouse audits
After Reviewing:
Update src/resources/AccessibilityStatement.mdx with findings and remediation plans.
- Axe CLI sometimes reports false positives for contrast on dynamically themed content
- Pre-hydration testing doesn't always capture themed states accurately
- Manual browser testing remains the most reliable method for theme-specific accessibility
- Semantic HTML5 structure
- Keyboard navigation support
- ARIA attributes for screen readers
- Responsive design across devices
- WCAG AA color contrast (light and dark themes)
- Automated testing suite integrated
- Future: Playwright + Axe-core for CI-integrated theme testing
- Future: Ensure all iconic buttons have discernible screen reader text
This project uses Yarn Plug'n'Play (PnP) instead of traditional node_modules.
Yarn PnP is a zero-install dependency resolution system:
- Dependencies resolve directly from
.pnp.cjs(the PnP manifest) - No
node_modulestree is created - Faster installs, smaller checkouts, deterministic resolution
- ✅ Faster CI: Smaller checkouts and faster dependency resolution
- ✅ Deterministic: No phantom packages hiding in sub-folders
- ✅ Better Editor Integration: Via Yarn SDKs (
.yarn/sdks/) - ✅ Security: Explicit dependency resolution prevents supply chain attacks
One-time setup after cloning:
yarn dlx @yarnpkg/sdks vscodeReplace vscode with your editor: vim, intellij, webstorm, etc.
What this does:
- Generates helper wrappers in
.yarn/sdks/ - Allows your IDE's TypeScript server, ESLint, and Prettier to traverse the PnP map
- Required for proper IntelliSense and linting
| File | Purpose | Commit? |
|---|---|---|
.pnp.cjs |
PnP manifest (part of lockfile) | ✅ Yes |
.pnp.loader.mjs |
ESM loader for PnP | ✅ Yes |
.yarn/sdks/** |
Editor wrappers for PnP-aware tooling | ✅ Yes |
.yarnrc.yml |
Yarn configuration (enables PnP, package extensions) | ✅ Yes |
Non-interactive upgrade:
yarn set version 4.10.3
corepack prepare yarn@4.10.3 --activate
yarn install && yarn dedupe --strategy=highestAfter upgrading:
- Commit the updated
.pnp.cjsandyarn.lock - Re-run
yarn dlx @yarnpkg/sdks vscodeif editor integration breaks - Verify editors still resolve packages correctly
Error: Cannot find module './.pnp.cjs'
- Ensure
corepack enablehas been run (requires admin on Windows) - Verify
.pnp.cjsexists and is committed to git - Check that scripts in
package.jsondon't manually require.pnp.cjs(Corepack handles this)
Error: Qualified path resolution failed
- Remove manual
-r ./.pnp.cjsprefixes from package.json scripts - Let Corepack manage the PnP environment
Missing peer dependency (e.g., acorn for recma-jsx):
Add to .yarnrc.yml:
packageExtensions:
recma-jsx@*:
dependencies:
acorn: '*'Configuration: vercel.json
The installCommand uses corepack enable && yarn install --immutable to ensure Vercel uses the correct Yarn version (v4, not v1 Classic).
Key Learnings:
- Vercel may default to Yarn v1 despite
packageManagerfield inpackage.json - Explicitly enabling Corepack in the install command forces correct version
- The
.pnp.cjsfile must be committed to git for deployment to succeed - Simplified build scripts (no manual PnP loader) prevent resolution errors
The project uses an explicit types array in tsconfig.json compilerOptions:
{
"compilerOptions": {
"types": [
"react",
"react-dom",
"next",
"jest",
"@testing-library/jest-dom",
"node"
]
}
}| Scenario | Explicit types (current) |
Omitted types |
|---|---|---|
| Behavior | Only listed packages are injected into global namespace | All @types/* packages auto-included |
| Pros | Deterministic globals, cleaner IntelliSense | Zero maintenance, types "just work" |
| Cons | Must remember to add every global type | Potential for hidden conflicts between libraries |
Project decision: We use explicit types to prevent test-only helpers (like jest-dom) from leaking into production builds, while ensuring all required runtime globals are available.
Alternative: Delete the types field entirely to use implicit behavior. Never leave it half-populated (e.g., only Jest) or JSX support will break.
tsconfig.json- Main configuration for production codetsconfig.dev.json- Extended configuration for development toolingtsconfig.jest.json- Specialized configuration for Jest tests
Usage:
yarn typecheck # Check production code
yarn type-check:dev # Check with dev tooling typesThe project uses shadcn/ui for accessible, customizable UI components.
Location: components/ui/
- Tailwind-powered: No runtime CSS overhead
- Accessible: Built on Radix UI primitives (ARIA-compliant)
- Customizable: Components are copied into your codebase, not installed as dependencies
- Type-safe: Full TypeScript support
- Accordion (
components/ui/accordion.tsx) - Button (
components/ui/button.tsx) - Card (
components/ui/card.tsx) - Checkbox (
components/ui/checkbox.tsx) - Label (
components/ui/label.tsx) - Radio Group (
components/ui/radio-group.tsx) - Switch (
components/ui/switch.tsx) - Tabs (
components/ui/tabs.tsx) - Tooltip (
components/ui/tooltip.tsx)
npx shadcn-ui@latest add <component>What happens:
- Component files are generated in
components/ui/ - Tailwind theme tokens are added to
tailwind.config.js(if needed) - Dependencies are automatically added to
package.json
After adding:
- Review the generated component
- Commit the new files
- Import and use:
import { Button } from '@/components/ui/button'
Style tokens are defined in tailwind.config.js under extend.colors:
/* eslint-disable @typescript-eslint/no-require-imports */
module.exports = {
theme: {
extend: {
colors: {
// shadcn/ui tokens
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
// ... etc
},
},
},
}Note: The ESLint disable comment is intentional - Tailwind's docs require CommonJS format (module.exports).
| Path | Generated by | Purpose | Commit? |
|---|---|---|---|
.yarn/sdks/** |
yarn sdks |
Editor wrappers for PnP-aware tooling | ✅ Yes |
.pnp.cjs & .pnp.loader.mjs |
Yarn | PnP mapping & loader (part of lockfile) | ✅ Yes |
components/ui/** |
shadcn-ui |
UI component source files | ✅ Yes |
accessibility-reports/** |
yarn access |
Accessibility audit reports | ❌ No (gitignored) |
.next/analyze/*.html |
yarn analyze |
Bundle size visualizations | ❌ No (gitignored) |
tsconfig.tsbuildinfo |
TypeScript | Build cache | ✅ Yes (tracked for optimization) |
Current configuration:
- Uses glob patterns to ignore large platform-specific binaries (e.g.,
**/@next/swc-*/**) - Excludes PnP cache (
.yarn/cache/**) for zero-install setup - Includes
.yarn/releases/**to version-lock Yarn binary - Excludes build artifacts (
.next/**,out/**,dist/**)
PnP-specific:
- ✅ Commit
.pnp.cjsand.pnp.loader.mjs - ✅ Commit
.yarn/sdks/for editor integration - ❌ Don't commit
.yarn/cache/(optional: enable for true zero-install) - ❌ Don't commit
.yarn/install-state.gz
yarn dev # Start development server
yarn lint # Check code quality
yarn typecheck # Check TypeScript types
yarn test # Run tests in watch modeyarn lint # Lint all files
yarn format # Format all files
yarn typecheck # Check types
yarn build # Build for production
yarn analyze # Analyze bundle size
yarn access # Run accessibility auditsyarn up # Update all dependencies
yarn dedupe # Remove duplicate packages
yarn audit # Full security vulnerability report
yarn audit:critical # Critical vulnerabilities only
yarn audit:fix # Attempt automatic fixesyarn cache clean # Clear Yarn cache
rm -rf .yarn/cache .pnp.cjs yarn.lock && yarn install # Nuclear option
yarn dlx @yarnpkg/sdks vscode # Regenerate editor SDKsConfiguration: vercel.json
Build Command: yarn build
- Runs
next build - Generates service worker via
scripts/build-sw.mjs - Generates sitemap via
next-sitemap
Install Command: corepack enable && yarn install --immutable
- Forces Yarn v4 (PnP mode)
--immutableensures lockfile isn't modified during build
Environment:
- Node.js 22.x (specified in
package.jsonengines) - Vercel automatically respects
packageManagerfield after Corepack is enabled
Security Audit Workflow: .github/workflows/security-audit.yml
Runs on PRs, pushes to main, weekly cron, and manual dispatch. See Security Auditing for details.
| Command | Purpose |
|---|---|
yarn dev |
Start development server |
yarn build |
Build for production |
yarn lint |
Lint all files |
yarn lint:mdx |
Lint only MDX files |
yarn format |
Format all files |
yarn format:mdx |
Format only MDX files |
yarn typecheck |
Check TypeScript types |
yarn test |
Run tests in watch mode |
yarn analyze |
Analyze bundle size |
yarn access |
Run accessibility audits |
yarn audit |
Full security audit |
yarn audit:critical |
Critical vulnerabilities only |
| File | Purpose |
|---|---|
eslint.config.mjs |
ESLint configuration |
.prettierrc.json |
Prettier configuration |
jest.config.js |
Jest configuration |
jest.setup.js |
Jest setup (imports jest-dom) |
tailwind.config.js |
Tailwind CSS configuration |
tsconfig.json |
TypeScript configuration |
next.config.js |
Next.js configuration |
vercel.json |
Vercel deployment configuration |
.yarnrc.yml |
Yarn configuration (PnP settings) |
package.json |
Dependencies and scripts |
Problem: Assets work locally on Windows (case-insensitive) but fail in production on Vercel/Linux (case-sensitive).
Symptoms: Files load in development but return 404 errors in production.
Solution: Use git mv oldName.png newName.png to rename files to match code references exactly.
# Example: Code references linkedin.png but git tracks Linkedin.png
git mv public/images/Linkedin.png public/images/linkedin.pngPrevention: Use consistent lowercase naming for all assets.
Problem: Defining global theme styles with :root or [data-theme='dark'] in CSS Module files causes build errors.
Solution: Define theme-specific classes within CSS Modules (e.g., .dropdownLight, .dropdownDark), then conditionally apply them in React:
const { theme } = useTheme()
const isDark = theme === 'dark'
<div className={`${styles.dropdown} ${isDark ? styles.dropdownDark : styles.dropdownLight}`}>Problem: Tailwind classes appear in HTML but styles don't apply.
Causes & Fixes:
-
contentarray missing paths: Ensuretailwind.config.jsincludes all directories:content: ['./src/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}']
-
Missing directives: Verify
global.csscontains:@tailwind base; @tailwind components; @tailwind utilities;
-
PostCSS conflict: Modern Next.js handles Tailwind without
postcss.config.js. Remove it if encountering issues.
Problem: Components imported in .mdx files don't render.
Solution: When using next-mdx-remote, components must be passed to <MDXRemote /> via the components prop:
// pages/resources/[slug].tsx
import { CustomComponent } from '@/components/CustomComponent'
;<MDXRemote {...source} components={{ CustomComponent }} />Tip: For dates in YYYY-MM-DD format, use localeCompare() directly:
posts.sort((a, b) => b.date.localeCompare(a.date)) // DescendingAvoid creating new Date() objects—string comparison is reliable and avoids TypeScript signature errors.
Symptoms: Property 'toBeInTheDocument' does not exist...
Fix:
- Ensure
"types": ["jest", "@testing-library/jest-dom"]intsconfig.json - Restart TypeScript server in your IDE
- If persisting:
rm -rf node_modules && yarn install
Symptoms: Expected the closing tag </Component>...
Fix:
- Add blank lines before/after JSX components in MDX
- Don't mix HTML tags (like
<br />) inside markdown lists - Ensure proper indentation for closing tags
- Run
yarn lint:mdxto validate
Symptoms: Prettier formats code, then ESLint complains
Fix:
- This shouldn't happen -
eslint-config-prettierdisables conflicting rules - If it does: check that
eslint-config-prettieris the last item in your ESLint extends array
Symptoms: Cannot find module or Qualified path resolution failed
Fix:
- Ensure
corepack enablehas been run - Remove manual
-r ./.pnp.cjsfrom package.json scripts - Regenerate editor SDKs:
yarn dlx @yarnpkg/sdks vscode - Verify
.pnp.cjsexists and is committed
Symptoms: Tests pass with yarn test but fail in CI
Fix:
- Ensure time zones are consistent (use
Date-fnswith explicit zones) - Mock
Math.random()and other non-deterministic functions - Use
--ciflag in CI:yarn test --ci --coverage - Check for file system case sensitivity (Windows vs Linux)