React Server Side Rendering (SSR)

Typescript

In our time it's easy to make react SSR using only Typescript.

Create directory

mkdir react-ssr-ts
cd react-ssr-ts

Init project

yarn init -y
echo "nodeLinker: node-modules" > .yarnrc.yml
touch yarn.lock

Dependencies

yarn add react react-dom typescript @types/react @types/react-dom

tsconfig.json

{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "jsx": "react-jsx",
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["index.tsx"]
}

index.tsx

import * as React from 'react';
import { renderToString } from 'react-dom/server';

const App = () => <div>App</div>;

console.log(renderToString(<App />));

Run

tsc && node index

Result

<div>App</div>

Vyriy

Pure TypeScript is enough for a small SSR example, but for production SSR a bundler is usually safer. The TypeScript compiler transpiles files, but it does not bundle or validate the full runtime dependency graph. With a bundler you run the server from the same resolved module graph that was checked during build: ESM/CommonJS interop, JSX transform, package exports, aliases, side-effect imports and external dependencies are handled in one place. It also gives you a predictable server artifact in dist, which is easier to test, deploy and compare in consumer tests.

The same we can make with Vyriy.

Create directory

mkdir react-ssr-vyriy
cd react-ssr-vyriy

Init project

yarn init -y
echo "nodeLinker: node-modules" > .yarnrc.yml
touch yarn.lock

Dependencies

yarn add react react-dom typescript @types/react @types/react-dom
yarn add @vyriy/webpack-config @vyriy/typescript-config webpack

tsconfig.json

{
  "extends": "@vyriy/typescript-config/index.json",
  "include": ["index.tsx"]
}

index.tsx

import { renderToString } from 'react-dom/server';

const App = () => <div>App</div>;

console.log(renderToString(<App />));

webpack.config.mjs

import 'webpack';

import { ssr } from '@vyriy/webpack-config';

export default ssr('./index.tsx', {
  filename: 'index.js',
  library: { type: 'commonjs2' },
  clean: true,
});

Run

webpack && node dist

Result

<div>App</div>

So yes, the bundler version has more code and one more config file. But this extra code is not accidental ceremony: it moves important runtime decisions into the build step. For SSR this is worth it because server rendering fails late and often in places that TypeScript alone does not model: package resolution, module format interop, side effects, missing files and dependencies that behave differently after publish.

The result is a stronger contract. If webpack builds the SSR entry, you know the server artifact can be executed from dist with the same dependency graph that your consumer or deployment will use. That makes the example a little longer, but the production story simpler: build once, test the built file, deploy the built file.

This is where Vyriy helps: the library takes the complicated webpack and TypeScript choices into shared configuration. The application keeps a small webpack.config.mjs, a normal TypeScript entry file and a plain webpack && node dist command. So the coding and build process stays simple, calm and reliable, while the hard parts are still handled deliberately.