# Vyriy Webpack Config

Shared Webpack configuration for Vyriy API, SSR, and CSR projects.

Published: 2026-05-13
Tags: vyriy, webpack, typescript, react, ssr, csr, api, config
Source: https://vyriy.dev/examples/vyriy-webpack-config/

---

# Vyriy Webpack Config

`@vyriy/webpack-config` is a shared Webpack configuration package for Vyriy projects.

The goal is to keep Webpack setup small and reusable across different runtime targets:

- TypeScript API server;
- React SSR server;
- React CSR browser application.

Each project keeps only the local entry point and output settings. The common Webpack behavior lives in `@vyriy/webpack-config`.

This article shows three small examples.

## Part 1: TypeScript API

The first example builds a small TypeScript API server.

It uses:

- `@vyriy/webpack-config` for the shared Webpack SSR/server build;
- `@vyriy/typescript-config` for the shared TypeScript baseline;
- `@vyriy/handler` for API handler wrapping;
- `@vyriy/server` for running the handler locally;
- `@vyriy/path` for clean output paths.

### Installation

```bash
yarn add @vyriy/path @vyriy/typescript-config @vyriy/webpack-config webpack webpack-cli @vyriy/handler @vyriy/server
```

### TypeScript config

Create `tsconfig.json`:

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

### API handler

Create `handler.ts`:

```ts
import { api } from '@vyriy/handler';

export const handler = api(async (event) => ({
  statusCode: 200,
  body: JSON.stringify({
    path: event.path,
  }),
}));
```

The handler returns a simple JSON response with the request path.

### Local server

Create `server.ts`:

```ts
import { server } from '@vyriy/server';
import { handler } from './handler';

server(handler);
```

This file connects the handler to the local server runtime.

### Webpack config

Create `webpack.config.mjs`:

```js
import 'webpack';

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

export default ssr('./server', {
  path: path('dist', 'api'),
  filename: 'index.js',
  library: { type: 'commonjs2' },
  clean: true,
});
```

The `ssr` helper is used here because this bundle targets Node.js, not the browser.

The output file will be:

```text
dist/api/index.js
```

### Build API bundle

Run Webpack in production mode:

```bash
NODE_ENV=production npx webpack --mode production
```

Output:

```bash
asset index.js 7.64 KiB [emitted] [minimized] (name: main)
orphan modules 25.6 KiB [orphan] 65 modules
./server.ts + 52 modules 22.7 KiB [not cacheable] [built] [code generated]
webpack 5.106.2 compiled successfully in 560 ms
```

### Run API server

Run the bundled server:

```bash
node dist/api
```

Output:

```bash
http://127.0.0.1:3000/
http://192.168.86.34:3000/
```

Open the local URL in a browser or call it with `curl`.

Response:

```json
{ "path": "/" }
```

This confirms that the TypeScript API was bundled and started successfully.

---

## Part 2: React SSR

The second example builds a React SSR server.

It uses the same server-side Webpack config, but the handler renders a React component to an HTML string.

### Installation

```bash
yarn add @vyriy/path @vyriy/typescript-config @vyriy/webpack-config webpack webpack-cli @vyriy/handler @vyriy/server react react-dom @types/react @types/react-dom
```

### TypeScript config

Create `tsconfig.json`:

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

This time the project includes both TypeScript and TSX files.

### React component

Create `app.tsx`:

```tsx
export const App = () => <div>App</div>;
```

### SSR handler

Create `handler.tsx`:

```tsx
import { renderToString } from 'react-dom/server';
import { api } from '@vyriy/handler';
import { App } from './app';

export const handler = api(async (event) => ({
  statusCode: 200,
  body: JSON.stringify({
    html: renderToString(<App />),
  }),
}));
```

The handler renders `<App />` with `renderToString` and returns the generated HTML inside a JSON response.

### Local server

Create `server.ts`:

```ts
import { server } from '@vyriy/server';
import { handler } from './handler';

server(handler);
```

### Webpack config

Create `webpack.config.mjs`:

```js
import 'webpack';

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

export default ssr('./server', {
  path: path('dist', 'api'),
  filename: 'index.js',
  library: { type: 'commonjs2' },
  clean: true,
});
```

This is almost the same as the API example. The main difference is the application code: now the server bundle includes React SSR logic.

### Build SSR bundle

Run Webpack:

```bash
NODE_ENV=production npx webpack --mode production
```

Output:

```bash
asset index.js 213 KiB [emitted] [minimized] (name: main)
orphan modules 25.9 KiB [orphan] 66 modules
built modules 557 KiB [built]
  modules by path ./ 557 KiB
    modules by path ./node_modules/react-dom/ 516 KiB
      modules by path ./node_modules/react-dom/cjs/*.js 514 KiB 3 modules
      modules by path ./node_modules/react-dom/*.js 1.98 KiB 2 modules
    modules by path ./node_modules/react/ 18.2 KiB
      modules by path ./node_modules/react/*.js 396 bytes 2 modules
      modules by path ./node_modules/react/cjs/*.js 17.8 KiB 2 modules
    ./server.ts + 53 modules 23 KiB [not cacheable] [built] [code generated]
  external "util" 42 bytes [built] [code generated]
  external "crypto" 42 bytes [built] [code generated]
  external "async_hooks" 42 bytes [built] [code generated]
  external "stream" 42 bytes [built] [code generated]
webpack 5.106.2 compiled successfully in 1141 ms
```

### Run SSR server

Run the bundled server:

```bash
node dist/api
```

Output:

```bash
http://127.0.0.1:3000/
http://192.168.86.34:3000/
```

Response:

```json
{ "html": "<div>App</div>" }
```

This confirms that the React component was rendered on the server.

---

## Part 3: React CSR

The third example builds a browser-side React application.

It uses:

- `csr` from `@vyriy/webpack-config`;
- `@vyriy/browserslist-config` for browser targets;
- `@vyriy/html` to create a minimal HTML template;
- `html-webpack-plugin` to generate `index.html`.

### Installation

```bash
yarn add @vyriy/path @vyriy/typescript-config @vyriy/webpack-config webpack webpack-cli react react-dom @types/react @types/react-dom @vyriy/browserslist-config @vyriy/html html-webpack-plugin
```

### Browserslist config

Create `.browserslistrc`:

```ini
[development]
extends @vyriy/browserslist-config

[ssr]
extends @vyriy/browserslist-config

[production]
extends @vyriy/browserslist-config

[modern]
extends @vyriy/browserslist-config
```

This keeps browser targets shared and consistent across Vyriy projects.

### TypeScript config

Create `tsconfig.json`:

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

### React component

Create `app.tsx`:

```tsx
export const App = () => <div>App</div>;
```

### Browser entry point

Create `index.tsx`:

```tsx
import { createRoot } from 'react-dom/client';
import { App } from './app';

createRoot(document.getElementById('root')!).render(<App />);
```

This is the client-side entry point. It mounts the React application into the HTML element with `id="root"`.

### Webpack config

Create `webpack.config.mjs`:

```js
import 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';

import { csr } from '@vyriy/webpack-config';
import { path } from '@vyriy/path';
import { html } from '@vyriy/html';

export default csr(
  './index',
  {
    path: path('dist'),
    filename: 'index.js',
    clean: true,
  },
  (config) => {
    return {
      ...config,
      plugins: [
        ...(config.plugins ?? []),
        new HtmlWebpackPlugin({
          templateContent: html({
            body: '<div id="root"></div>',
          }),
          publicPath: '/',
          hash: true,
          inject: 'body',
          minify: {
            removeComments: true,
            collapseWhitespace: true,
            removeAttributeQuotes: false,
            minifyJS: true,
            minifyCSS: true,
          },
        }),
      ],
    };
  },
);
```

The `csr` helper creates a browser-oriented Webpack config.

The third argument is a small extension function. It receives the generated config and returns an updated version with `HtmlWebpackPlugin` added.

This keeps the default shared config reusable, while still allowing the local project to add its own plugins.

### Build CSR bundle

Run Webpack:

```bash
NODE_ENV=production npx webpack --mode production
```

Output:

```bash
asset index.html 160 bytes [emitted]
orphan modules 122 bytes [orphan] 1 module
modules by path ./node_modules/ 561 KiB
  modules by path ./node_modules/react-dom/ 533 KiB
    modules by path ./node_modules/react-dom/*.js 2.67 KiB 2 modules
    modules by path ./node_modules/react-dom/cjs/*.js 530 KiB 2 modules
  modules by path ./node_modules/react/ 18.2 KiB
    modules by path ./node_modules/react/*.js 396 bytes 2 modules
    modules by path ./node_modules/react/cjs/*.js 17.8 KiB 2 modules
  modules by path ./node_modules/scheduler/ 10.1 KiB
    ./node_modules/scheduler/index.js 194 bytes [built] [code generated]
    ./node_modules/scheduler/cjs/scheduler.production.js 9.94 KiB [built] [code generated]
./index.tsx + 1 modules 326 bytes [built] [code generated]
webpack 5.106.2 compiled successfully in 1388 ms
```

### Check output files

List the `dist` directory:

```bash
ls dist
```

Output:

```bash
index.html
index.js
```

The CSR build produces static files:

```text
dist/index.html
dist/index.js
```

These files can be served by any static file server or deployed to S3, CloudFront, GitHub Pages, GitLab Pages, Nginx, or another static hosting target.

## Why shared Webpack config?

Webpack can be powerful, but local configuration often becomes noisy.

A small project should not need to copy a large Webpack setup just to build a TypeScript server, an SSR handler, or a browser React entry point.

With `@vyriy/webpack-config`, the local project keeps only the important details:

```text
entry point
output directory
output filename
runtime target
small local extensions
```

The shared package owns the common behavior.

This makes the setup easier to reuse across Vyriy packages:

```text
api server      -> ssr('./server', ...)
react ssr       -> ssr('./server', ...)
react csr       -> csr('./index', ...)
```

The result is a calm Webpack setup: explicit where it matters, shared where repetition would create noise.

## Documentation

See the [Webpack config API documentation](https://vyriy.dev/storybook/?path=/docs/config-webpack--api).
