Building a Production-Grade React SSR Framework from Scratch

🇨🇳 中文版

Introduction

In the React ecosystem, Next.js stands as the representative SSR framework, beloved by developers for its out-of-the-box experience and powerful features. But for those who want complete control over the build process, deep customization of deployment strategies, or simply understanding SSR fundamentals, Next.js’s “black box” nature becomes a limitation.

I spent several months building a lightweight yet fully-featured React SSR framework from scratch — NSBP (Node React SSR by Webpack). Here’s the development journey, technical choices, core features, and practical experience gained.

Live Demo: https://nsbp.erishen.cn/ | GitHub: https://github.com/erishen/nsbp

Why Build Instead of Using Next.js?

Complete Control Over the Build Process

Next.js’s power comes from automation, but that means losing some control:


// Next.js build (black box)
next build
// → auto routing, auto code splitting, auto image optimization, hundreds of hidden steps

// NSBP — precise control over every step
// config/webpack.base.js
module.exports = {
  entry: {
    client: './src/client/index.tsx',
    server: './src/server/index.ts'
  },
  plugins: [
    new MiniCssExtractPlugin(),
    new TerserPlugin(),
    // add any Webpack plugin
  ]
}

Deep Customization of Deployment

Next.js’s Vercel deployment is convenient, but if you want to deploy on your own Linux server, use Docker Compose, configure reverse proxies and load balancing, or implement custom security policies — controlling your own Express server is advantageous.

Learning SSR Fundamentals

Writing SSR rendering logic by hand gives you deep understanding:


const sheet = new ServerStyleSheet()
const jsx = sheet.collectStyles(
  <Provider store={store}>
    <StaticRouter location={reqPath}>
      <Routes>{/* ... */}</Routes>
    </StaticRouter>
  </Provider>
)
const content = renderToString(jsx)
const styleTags = sheet.getStyleTags()

Technical Stack & Architecture

Category Choice Version Reason
React React 19 19.2.3 Latest, concurrent features, better type support
State Redux Toolkit Simplified Redux, built-in Thunk support
Router React Router DOM 7.12.0 Stable client-side routing
Code Split @loadable/component 5.15.0 Mature code splitting
Build Webpack 5 5.96.0 Modular, extensible, rich ecosystem
Server Express 5.2.1 Lightweight, rich middleware ecosystem
Styling Styled-components + Sass/Less Multiple choices, flexible switching
Language TypeScript 5.x Type safety, excellent DX

SSR Rendering Architecture


HTTP Request
    ↓
Express Middleware (Helmet, Rate Limit)
    ↓
SSR Render Layer:
  1. Create Redux Store
  2. Prefetch route data (loadData)
  3. renderToString → HTML
  4. Extract Styled-components styles
  5. Serialize state to window.context
    ↓
Return HTML + Initial State

Client Hydration


const App = () => {
  const store = useMemo(() => {
    if (isSEO() && window?.context?.state) {
      return getStore(window.context.state)
    }
    return getStore()
  }, [])

  return (
    <Provider store={store}>
      <BrowserRouter>
        <Routes>{/* ... */}</Routes>
      </BrowserRouter>
    </Provider>
  )
}

loadableReady(() => {
  hydrateRoot(document.getElementById('root')!, <App />)
})

Core Features

Three Rendering Modes

Mode URL Use Case
SSR (default) / or /?nsbp=1 SEO, fast initial load
CSR /?nsbp=0 Debugging, dev experience
SSR Fallback /?nsbp=1&from=link Internal links, allow client update

Smart SSR Data Prefetching

Server-side data prefetch to avoid client-side re-requests:


// src/Routers.tsx
export default [{
  path: '/',
  element: <Home />,
  loadData: homeLoadData,  // ← prefetch on SSR
  key: 'home'
}]

Performance comparison: Traditional CSR ~2-3 seconds; NSBP SSR ~0.5-1 second.

Code Splitting & Lazy Loading


import loadable from '@loadable/component'
const Home = loadable(() => import('@containers/Home'), { fallback: <Loading /> })

Initial load: ~200KB main bundle, only home page code. Other pages loaded on demand.

Multi-Layer Security

Helmet security headers: CSP, X-Frame-Options, X-Content-Type-Options, HSTS

Rate limiting: 100 requests per 15 minutes on API routes

Docker One-Click Deployment


# docker-compose.yml
services:
  app:
    build:
      context: .
      dockerfile: docker/Dockerfile
    ports: ["8081:3001"]
    environment: [NODE_ENV=production, ENABLE_RATE_LIMIT=1]
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "node", "-e", "require('http').get(...)"]

Developer Experience

Quality toolchain: ESLint + Prettier + Husky pre-commit hooks with lint-staged

CLI tool: npx nsbp-cli create my-app for project scaffolding

Make commands: make dev, make prod, make logs, make shell

Performance Optimizations

Tree shaking: usedExports: true, sideEffects: false

Code minification: TerserPlugin with multi-process compression, drop_console in production

Static caching: 1-year immutable cache for JS/CSS/images

Image dimension probing: probe-image-size during SSR to avoid client re-requests

When to Use (and Not Use) NSBP

Good for: personal projects, rapid prototypes, education/learning, highly customized projects, low-resource environments

Not ideal for: large enterprise apps, rapid iteration projects, AI/ML features

Problems Solved

React 19 Hydration Error #418: Replaced dangerouslySetInnerHTML scripts with useEffect

Docker port config: Moved docker-compose.yml to root directory for .env access

Webpack Dev Server hot reload: Correctly configured Browser Sync Webpack Plugin

Future Plans

React Server Components, tRPC integration, Vitest migration, Service Worker caching, CDN support, ISR implementation

Conclusion

Building NSBP was an invaluable learning experience. While it may not replace Next.js, I deeply understood every link in modern frontend engineering, mastered building complete applications from scratch, and developed problem-solving patterns for complex systems.

Framework choice has no absolute right or wrong — only suitability. Next.js suits rapid development and most scenarios, but if you want complete control, deep learning, or special requirements, building from scratch is also a valid choice.

Links: Live Demo | GitHub | CLI: npx nsbp-cli create my-app

首页 Links 关于 隐私政策 GitHub

© 2026 Erishen
沪ICP备2024079226号-1   沪公网安备31010502007082号