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-configfor the shared Webpack SSR/server build;@vyriy/typescript-configfor the shared TypeScript baseline;@vyriy/handlerfor API handler wrapping;@vyriy/serverfor running the handler locally;@vyriy/pathfor clean output paths.
Installation
yarn add @vyriy/path @vyriy/typescript-config @vyriy/webpack-config webpack webpack-cli @vyriy/handler @vyriy/server
TypeScript config
Create tsconfig.json:
{
"extends": "@vyriy/typescript-config/index.json",
"include": ["*.ts"]
}
API handler
Create handler.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:
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:
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:
dist/api/index.js
Build API bundle
Run Webpack in production mode:
NODE_ENV=production npx webpack --mode production
Output:
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:
node dist/api
Output:
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:
{ "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
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:
{
"extends": "@vyriy/typescript-config/index.json",
"include": ["*.ts", "*.tsx"]
}
This time the project includes both TypeScript and TSX files.
React component
Create app.tsx:
export const App = () => <div>App</div>;
SSR handler
Create handler.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:
import { server } from '@vyriy/server';
import { handler } from './handler';
server(handler);
Webpack config
Create webpack.config.mjs:
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:
NODE_ENV=production npx webpack --mode production
Output:
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:
node dist/api
Output:
http://127.0.0.1:3000/
http://192.168.86.34:3000/
Response:
{ "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:
csrfrom@vyriy/webpack-config;@vyriy/browserslist-configfor browser targets;@vyriy/htmlto create a minimal HTML template;html-webpack-pluginto generateindex.html.
Installation
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:
[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:
{
"extends": "@vyriy/typescript-config/index.json",
"include": ["*.ts", "*.tsx"]
}
React component
Create app.tsx:
export const App = () => <div>App</div>;
Browser entry point
Create index.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:
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:
NODE_ENV=production npx webpack --mode production
Output:
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:
ls dist
Output:
index.html
index.js
The CSR build produces static files:
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:
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:
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.