Skip to content

Static Site Generation and Server-Side Rendering

Understanding how Rspress builds and renders your documentation is crucial for optimization and deployment. This chapter explores Static Site Generation (SSG), Server-Side Rendering (SSR), and the build process in Rspress.

Understanding SSG vs SSR

Static Site Generation (SSG)

SSG pre-renders all pages at build time, generating static HTML files:

Build Time:
Markdown/MDX → HTML + CSS + JS → Static Files

Runtime:
User Request → Server → Static HTML (Fast!)

Advantages:

  • Extremely fast page loads
  • Better SEO (content immediately available)
  • Lower server costs (just serve static files)
  • Works on simple hosting (no server required)
  • Can be cached on CDN

Best For:

  • Documentation sites
  • Blogs
  • Marketing pages
  • Content that doesn't change frequently

Server-Side Rendering (SSR)

SSR renders pages on each request:

Runtime:
User Request → Server Renders HTML → Response

Each request generates fresh HTML

Advantages:

  • Always up-to-date content
  • Can personalize per user
  • Access to request data
  • Dynamic content generation

Best For:

  • User dashboards
  • Personalized content
  • Frequently changing data
  • Dynamic applications

Rspress's Approach

Rspress primarily uses SSG with optional SSR capabilities:

typescript
// Default: Pure SSG
npm run build  // Generates static HTML files

// With SSR: Hybrid approach (advanced)
// Rspress can also do client-side hydration

Build Process

Build Pipeline

Understanding Rspress's build pipeline:

mermaid
graph LR
    A[Source Files] --> B[Parse MDX/MD]
    B --> C[Process Components]
    C --> D[Generate Routes]
    D --> E[Bundle Assets]
    E --> F[Generate HTML]
    F --> G[Optimize Output]
    G --> H[Static Files]

Build Command

bash
# Development build (fast, no optimization)
npm run dev

# Production build (optimized)
npm run build

# Preview production build
npm run preview

Build Output

After building, Rspress generates:

doc_build/
├── index.html                    # Homepage
├── guide/
│   ├── index.html               # /guide/
│   ├── installation.html        # /guide/installation
│   └── configuration.html       # /guide/configuration
├── assets/
│   ├── chunks/                  # Code-split JS chunks
│   ├── css/                     # Compiled CSS
│   └── images/                  # Optimized images
└── _rspress/
    └── metadata.json            # Build metadata

Build Configuration

Basic Build Settings

Configure the build process:

typescript
// rspress.config.ts
import { defineConfig } from 'rspress/config';

export default defineConfig({
  root: 'docs',
  outDir: 'doc_build',  // Output directory

  // Build configuration
  builderConfig: {
    // Output format
    output: {
      cleanDistPath: true,  // Clean output before build
      distPath: {
        root: 'doc_build',
      },
    },

    // Performance
    performance: {
      chunkSplit: {
        strategy: 'split-by-experience',
      },
    },
  },
});

Optimization Settings

Optimize build output:

typescript
export default defineConfig({
  builderConfig: {
    // Minification
    output: {
      minify: {
        js: true,
        css: true,
        html: true,
      },
    },

    // Code splitting
    performance: {
      chunkSplit: {
        strategy: 'split-by-experience',
        forceSplitting: [
          /node_modules/,
        ],
      },
    },

    // Source maps (for debugging)
    output: {
      sourceMap: {
        js: 'source-map',  // or false for production
        css: true,
      },
    },
  },
});

Pre-rendering

How Pre-rendering Works

Rspress pre-renders pages using React SSR:

  1. Parse Content: Read and parse Markdown/MDX
  2. Execute React: Run React components to generate HTML
  3. Serialize State: Capture application state
  4. Generate HTML: Create complete HTML with inlined state
  5. Optimize: Minify and optimize output

Pre-render Configuration

Control pre-rendering behavior:

typescript
export default defineConfig({
  // Pre-render all routes
  ssg: {
    strict: true,  // Fail build on errors
  },

  // Route-specific configuration
  route: {
    // Routes to pre-render
    include: ['/', '/guide/**', '/api/**'],

    // Routes to skip
    exclude: ['**/draft/**'],
  },
});

Hydration

What is Hydration?

Hydration makes static HTML interactive:

1. Server sends static HTML (fast initial load)
2. Browser downloads JavaScript
3. React "hydrates" the HTML (attaches event listeners)
4. Page becomes fully interactive

Hydration in Rspress

tsx
// During build (SSG)
<button>Click Me</button>

// After hydration (interactive)
<button onClick={handleClick}>Click Me</button>

Optimizing Hydration

typescript
// Lazy load heavy components
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

export function Page() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

Code Splitting

Automatic Code Splitting

Rspress automatically splits code by route:

Homepage:     homepage.js (50KB)
Guide Page:   guide.js (30KB)
API Page:     api.js (40KB)
Shared:       vendor.js (100KB)

Manual Code Splitting

Split large components:

tsx
// Instead of this (loads everything immediately)
import { Chart } from './Chart';
import { Table } from './Table';
import { Graph } from './Graph';

// Do this (loads on demand)
const Chart = lazy(() => import('./Chart'));
const Table = lazy(() => import('./Table'));
const Graph = lazy(() => import('./Graph'));

Route-based Splitting

Rspress automatically splits by route:

typescript
// Each route gets its own bundle
/guide/        → guide-route.js
/guide/start   → guide-start-route.js
/api/config    → api-config-route.js

Build Performance

Measuring Build Time

bash
# Measure build time
time npm run build

# Enable verbose logging
npm run build -- --verbose

# Analyze bundle size
npm run build -- --analyze

Optimization Strategies

  1. Reduce Dependencies
typescript
// Heavy library (100KB)
import _ from 'lodash';

// Lighter alternative (10KB)
import debounce from 'lodash/debounce';
  1. Lazy Load Heavy Content
mdx
import { lazy } from 'react';

const VideoPlayer = lazy(() => import('./VideoPlayer'));

<Suspense fallback="Loading...">
  <VideoPlayer />
</Suspense>
  1. Optimize Images
bash
# Before adding to docs
imagemin docs/images/* --out-dir=docs/images/optimized

Caching

Enable build caching for faster rebuilds:

typescript
export default defineConfig({
  builderConfig: {
    // Enable caching
    cache: true,
  },
});

Build Modes

Development Mode

Fast builds with hot reload:

bash
npm run dev

# Features:
- Fast incremental builds
- Hot module replacement
- Source maps enabled
- No minification
- Verbose error messages

Production Mode

Optimized builds for deployment:

bash
npm run build

# Features:
- Full optimization
- Minification enabled
- Tree shaking
- Dead code elimination
- Asset optimization

Preview Mode

Test production build locally:

bash
# Build first
npm run build

# Then preview
npm run preview

# Access at http://localhost:4173

Advanced Build Features

Custom Build Scripts

Add custom build scripts:

json
// package.json
{
  "scripts": {
    "build": "rspress build",
    "build:analyze": "rspress build --analyze",
    "build:staging": "NODE_ENV=staging rspress build",
    "build:production": "NODE_ENV=production rspress build"
  }
}

Environment Variables

Use environment variables in builds:

typescript
// rspress.config.ts
export default defineConfig({
  define: {
    'process.env.API_URL': JSON.stringify(
      process.env.NODE_ENV === 'production'
        ? 'https://api.production.com'
        : 'https://api.staging.com'
    ),
  },
});
tsx
// Use in components
const apiUrl = process.env.API_URL;

Build Hooks

Run custom logic during build:

typescript
export default defineConfig({
  plugins: [
    {
      name: 'custom-build-plugin',
      async buildStart() {
        console.log('Build starting...');
      },
      async buildEnd() {
        console.log('Build complete!');
        // Post-build tasks
      },
    },
  ],
});

Incremental Builds

Understanding Incremental Builds

Only rebuild changed pages:

bash
# First build (all pages)
npm run build  # 30 seconds

# Change one file
# Second build (only changed page)
npm run build  # 5 seconds

Enabling Incremental Builds

typescript
export default defineConfig({
  builderConfig: {
    cache: {
      buildDependencies: {
        config: [__filename],
      },
    },
  },
});

SEO Optimization

Meta Tags

Add meta tags during build:

typescript
export default defineConfig({
  head: [
    ['meta', { name: 'description', content: 'Site description' }],
    ['meta', { name: 'keywords', content: 'keywords, here' }],
    ['meta', { property: 'og:title', content: 'Site Title' }],
    ['meta', { property: 'og:description', content: 'Description' }],
  ],
});

Sitemap Generation

Generate sitemap during build:

typescript
export default defineConfig({
  plugins: [
    {
      name: 'sitemap-generator',
      async buildEnd(routes) {
        const sitemap = generateSitemap(routes);
        await fs.writeFile('doc_build/sitemap.xml', sitemap);
      },
    },
  ],
});

Robots.txt

Add robots.txt:

# public/robots.txt
User-agent: *
Allow: /

Sitemap: https://example.com/sitemap.xml

Performance Monitoring

Bundle Analysis

Analyze bundle size:

bash
# Generate bundle analysis
npm run build -- --analyze

# Opens visualization in browser
# Shows:
# - Bundle sizes
# - Chunk composition
# - Dependency tree

Lighthouse Scores

Test your built site:

bash
# Install Lighthouse
npm install -g lighthouse

# Build and preview
npm run build
npm run preview

# Run Lighthouse
lighthouse http://localhost:4173 --view

Core Web Vitals

Monitor performance metrics:

  • LCP (Largest Contentful Paint): < 2.5s
  • FID (First Input Delay): < 100ms
  • CLS (Cumulative Layout Shift): < 0.1

Troubleshooting

Build Failures

  1. Memory Issues
bash
# Increase Node memory
NODE_OPTIONS=--max_old_space_size=4096 npm run build
  1. Missing Dependencies
bash
# Clean install
rm -rf node_modules package-lock.json
npm install
  1. Build Errors
bash
# Verbose logging
npm run build -- --verbose

# Check specific file
npm run build -- --debug

Slow Builds

  1. Disable source maps in production
  2. Reduce dependencies
  3. Use build caching
  4. Optimize images before building
  5. Split large pages into smaller ones

Large Bundle Sizes

bash
# Analyze what's included
npm run build -- --analyze

# Common culprits:
# - Large libraries (lodash, moment.js)
# - Duplicate dependencies
# - Unoptimized images
# - Unused CSS

Best Practices

Build Checklist

Before deploying:

  • [ ] Run production build
  • [ ] Test with preview
  • [ ] Check bundle sizes
  • [ ] Verify all pages load
  • [ ] Test on mobile
  • [ ] Check Lighthouse scores
  • [ ] Validate HTML
  • [ ] Test all links

CI/CD Integration

yaml
# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run build
      - run: npm run test

Next Steps


Performance Tips

  • Enable build caching for faster rebuilds
  • Use code splitting for large sites
  • Optimize images before adding them
  • Monitor bundle sizes regularly

Production Builds

  • Always test production builds before deploying
  • Enable minification in production
  • Disable source maps in production
  • Use environment variables for configs

Content is for learning and research only.