Chapter 15: Code Quality (Biome) and Content (Fumadocs)
In Chapter 14, we established a solid CI/CD pipeline that acts as the 'gatekeeper' for our SAAS application. One of the core responsibilities of this gatekeeper is running pnpm lint and pnpm typecheck. But what's actually working behind these commands?
Chapter 15: Code Quality (Biome) and Content (Fumadocs)
In Chapter 14, we established a solid CI/CD pipeline that acts as the "gatekeeper" for our SAAS application. One of the core responsibilities of this gatekeeper is running pnpm lint and pnpm typecheck. But what's actually working behind these commands?
At the same time, a SAAS without documentation is a black box. Users (and even your future self) won't be able to understand how to use your product.
In this chapter, we'll dive deep into two key tools that ensure code quality and content delivery: Biome (our code steward) and Fumadocs (our content delivery network).
15.1. Why Biome? (vs. ESLint + Prettier)
For most JavaScript/TypeScript developers (including those migrating from Python), "code quality" typically means a "double combo": ESLint (for code linting and rule checking) + Prettier (for code formatting).
This combination has been the standard for the past few years, but it also brings obvious pain points:
- Configuration Hell: You need to maintain multiple configuration files:
.eslintrc.js,.prettierrc,.eslintignore,.prettierignore, etc. - Conflicts: ESLint's formatting rules often conflict with Prettier's rules. You need to install
eslint-config-prettierandeslint-plugin-prettierto "turn off" ESLint's rules and let Prettier take over. - Performance: Both are written in JavaScript. In a large project, running
eslint . --fixandprettier . --writecan take tens of seconds or longer. - Plugin Dependencies: You need to install and configure a bunch of plugins for TypeScript (
@typescript-eslint/parser), React (eslint-plugin-react), Imports (eslint-plugin-import), etc.
Biome (formerly Rome) is the modern answer to this problem.
Biome is a high-performance all-in-one toolchain written in Rust, designed to replace ESLint, Prettier, tsc (some lint functionality), and more tools in the future.
Why did our SAAS template choose Biome?
- Blazing Fast: Written in Rust, it's orders of magnitude faster than the ESLint + Prettier combo. In CI,
pnpm lintruntime drops from tens of seconds to a few seconds. - Single Tool: One executable, one
biome.jsonconfiguration file, one command (biome checkorbiome format) is all you need. - Unified: Linting and Formatting are handled by the same engine, sharing the same AST (Abstract Syntax Tree), fundamentally eliminating conflicts between the two.
- Powerful Defaults: Biome provides very reasonable "recommended" rule sets that work out of the box, greatly reducing configuration burden.
- Built-in Features: It natively supports TypeScript, JSX, automatic import sorting (
organizeImports), without any plugins.
From a Python developer's perspective, this is like no longer needing three tools (flake8 + black + isort), but having one lightning-fast tool that handles everything.
15.2. [Code Analysis]: Analyzing biome.json Configuration
All of Biome's configuration is concentrated in the biome.json file in the project root. Here's an extremely simple configuration example:
// biome.json
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
// Specify the language used by the project
"javascript": {
"parser": {
// Allow unsafe 'any' types but warn about them
// Useful in transition projects
"unsafeParameterDecorators": true
},
// Specify Biome's formatter
"formatter": {
"semicolons": "asNeeded", // Add semicolons as needed
"quoteStyle": "single", // Prefer single quotes
"trailingComma": "all" // Trailing commas
}
},
// Formatter master switch
"formatter": {
"enabled": true,
"formatWithErrors": false, // Try to format even if code has lint errors
"indentStyle": "space", // Use space indentation
"indentWidth": 2, // Indent 2 spaces
"lineWidth": 80, // Line width 80 characters
"ignore": ["node_modules/", "dist/"] // Ignored directories
},
// Linter master switch
"linter": {
"enabled": true,
// Enable Biome's recommended rule set
"rules": {
"recommended": true,
// (Example) You can override specific rules
"suspicious": {
"noExplicitAny": "warn" // Downgrade "noExplicitAny" rule from "error" to "warn"
}
}
},
// Auto-organize imports
"organizeImports": {
"enabled": true
}
}Configuration Breakdown:
$schema: Provides autocomplete and validation in editors like VS Code.formatter: Defines all formatting rules, like 2-space indentation, 80-character line width. This single configuration section completely replaces Prettier.linter: Defines lint rules."recommended": trueautomatically enables a carefully selected core set of rules, replacing ESLint and its host of plugins.organizeImports: Replaceseslint-plugin-importorisort(in Python), automatically sorting and grouping import statements.
In package.json, our scripts become extremely simple:
"scripts": {
// ...
"lint": "biome check .",
"lint:fix": "biome check --apply .",
"format": "biome format --write ."
}In the GitHub Actions from Chapter 14, our pnpm lint (i.e., biome check .) is now an ultra-fast, unified quality check.
15.3. SAAS Documentation Site: Fumadocs
A SAAS product without documentation doesn't exist. You need a place to host:
- User Guides: How to register, how to use feature A, how to configure B.
- API Reference: If your SAAS provides an API, developers need to consult it.
- Tutorials and Guides: "How to solve [X problem] with our SAAS".
You need a documentation tool that must:
- Be easy to write (typically Markdown).
- Be compatible with your tech stack (Next.js 15+ App Router).
- Be fast, supporting modern features like search, dark mode, etc.
Fumadocs is a modern documentation generator built specifically for this. Unlike Docusaurus or GitBook, Fumadocs is a "Next.js-first" toolkit that leverages Next.js's App Router to build documentation sites, not a separate system.
Why choose Fumadocs?
- Based on App Router: It's not a "black box" framework but gives you a set of tools and components to use within Next.js App Router.
- MDX-Powered: You can seamlessly use React components in Markdown (
.mdx) files. - High Performance: Because it is Next.js, it enjoys all performance benefits like RSC, static generation, etc.
- Feature-Complete: Built-in search (powered by
next-docs-zeta), file tree navigation, i18n support, dark mode, etc.
15.4. [Code Analysis]: Analyzing content/ Directory and pnpm content
In our project, documentation content is stored in the content/ folder in the project root and served by our Next.js application (under the src/app/[locale]/docs/ route).
content/ Directory Structure
Fumadocs relies on a clear folder structure and uses _meta.json files to define the order and titles of sidebars.
.
├── content/
│ └── docs/
│ ├── _meta.json # (Defines "Root" sidebar)
│ ├── 01.introduction.mdx # Introduction
│ ├── 02.getting-started/ # "Getting Started" folder
│ │ ├── _meta.json
│ │ ├── 01.installation.mdx
│ │ └── 02.configuration.mdx
│ └── 03.api-reference/ # "API" folder
│ ├── _meta.json
│ ├── 01.auth.mdx
│ └── 02.payments.mdx
└── src/
└── app/
└── [locale]/
└── docs/ # Next.js routes to render 'content/'
└── [[...slug]]/
├── layout.tsx
└── page.tsx_meta.json File Breakdown
This _meta.json file is Fumadocs's "glue," telling Next.js how to organize pages in the content/ directory.
// content/docs/_meta.json
{
"title": "Documentation", // Sidebar group title
"pages": [
"introduction",
"getting-started", // Link to 'getting-started' folder
"---", // A separator
"api-reference" // Link to 'api-reference' folder
]
}
// content/docs/02.getting-started/_meta.json
{
"title": "Getting Started", // Title for this group
"pages": [
"installation",
"configuration"
]
}.mdx File Breakdown
The introduction.mdx file is just a mix of Markdown and React.
# Introduction
Welcome to our SAAS platform!
This is documentation written in MDX. You can use **standard Markdown** here.
<Callout type="warning">
You can also use custom React components defined in your codebase!
</Callout>The pnpm content Command?
In the context of this book, pnpm content is a fictional script we use for processing documentation content. In actual projects, it might represent:
pnpm docs:dev: A script defined inpackage.json, like"docs:dev": "next dev -p 3001", specifically for running the documentation site locally (if the documentation site is a separate app).pnpm content:validate: A script usingcontentlayeror similar tools to validate that all.mdxfiles' frontmatter conforms to the schema at build time.
For our integrated SAAS template, the content/ directory is just dynamically read by the src/app/.../docs routes. Therefore, no separate pnpm content command is needed. Running pnpm dev starts both the main app and documentation pages. Any changes in the content/ directory trigger Next.js's hot reload, just like changes in the src/app directory.
Chapter 15 Summary
In this chapter, we added two critical layers of "safety net" to our SAAS project:
- Code Quality (Biome): We replaced the traditional (and slow) ESLint + Prettier combo with a unified, ultra-fast Rust-based tool Biome. Through a single
biome.jsonfile, we unified Linting and Formatting, ensuring codebase consistency and greatly accelerating the CI process. - Content Delivery (Fumadocs): We chose Fumadocs as our documentation solution. Because it's deeply integrated with Next.js App Router, we can write rich documentation in MDX in the
content/directory while enjoying the same tech stack, performance, and development experience as our main SAAS application.
With Biome ensuring "internal quality" and Fumadocs ensuring "external clarity," our SAAS project has taken a major step forward in maintainability and user-friendliness.
Categories
content/ Directory Structure_meta.json File Breakdown.mdx File BreakdownThe pnpm content Command?Chapter 15 SummaryMore Posts
Chapter 11: Payments and Subscriptions (Stripe)
Welcome to Part Five, the core business logic of our SAAS application. In previous chapters, we built a solid application foundation—from frontend UI, RSC data flow, secure Server Actions to type-safe Drizzle ORM and 'better-auth' authentication. Now, it's time to transform our application from a 'project' into a 'product': implementing paid subscriptions.
Chapter 17: Performance Monitoring & Observability
Our SAAS application is now feature-complete, having passed through rigorous CI/CD pipelines (Chapter 14) and automated testing (Chapter 16). It's deployed to production on Vercel. But once our app 'leaves' our development and testing environment and enters the unpredictable devices and networks of real users, how do we know it's running well?
Chapter 21: [Tool] Architect's Debugging Weapon: next-devtools-mcp
Welcome to the final chapter of this book. In Chapter 20, we confronted the most headache-inducing traps in Next.js 15+ architecture, especially React Server Components (RSC) caching issues and various anti-patterns. You might be thinking: 'I understand the theory, but when my app actually has cache not refreshing or data corruption, how do I real-time see what's happening inside the 'black box' like Python's debugger (PDB)...'