website/docs/en/config/dev-server.mdx
import WebpackLicense from '@components/WebpackLicense'; import { Tabs, Tab } from '@theme';
<WebpackLicense from="https://webpack.js.org/configuration/dev-server/" />This page describes the options used by @rspack/dev-server to run a local development server for Rspack projects.
objectThe devServer config only takes effect when using @rspack/dev-server. If the current application does not start @rspack/dev-server, this config will have no effect.
For example, with Rspack CLI, install @rspack/dev-server and use rspack dev or rspack serve to apply the devServer config. Rsbuild implements its own dev server and provides a separate server config, so Rsbuild projects should configure that instead.
When using rspack preview, the Rspack CLI also loads @rspack/dev-server and respects a subset of devServer options: port, host, open, server, proxy, and historyApiFallback.
string | string[] | 'all' | 'auto''auto'This option allows you to allowlist services that are allowed to access the dev server.
export default {
devServer: {
allowedHosts: [
'host.com',
'subdomain.host.com',
'subdomain2.host.com',
'host2.com',
],
},
};
Mimicking Django's ALLOWED_HOSTS, a value beginning with . can be used as a subdomain wildcard. .host.com will match host.com, www.host.com, and any other subdomain of host.com.
export default {
devServer: {
// this achieves the same effect as the first example
// with the bonus of not having to update your config
// if new subdomains need to access the dev server
allowedHosts: ['.host.com', 'host2.com'],
},
};
When set to 'all' this option bypasses host checking. THIS IS NOT RECOMMENDED as apps that do not check the host are vulnerable to DNS rebinding attacks.
export default {
devServer: {
allowedHosts: 'all',
},
};
When set to 'auto' this option always allows localhost, host, and client.webSocketURL.hostname:
export default {
devServer: {
allowedHosts: 'auto',
},
};
object'log' | 'info' | 'warn' | 'error' | 'none' | 'verbose''info'Allows to set log level in the browser, e.g. before reloading, before an error or when HMR is enabled.
export default {
devServer: {
client: {
logging: 'info',
},
},
};
boolean | objecttrueShows a full-screen overlay in the browser when there are compiler errors or warnings.
export default {
devServer: {
client: {
overlay: true,
},
},
};
You can provide an object with the following properties for more granular control:
| Property | Explanation |
|---|---|
errors | compilation errors |
runtimeErrors | unhandled runtime errors |
warnings | compilation warnings |
All properties are optional and default to true when not provided.
For example, to disable compilation warnings, you can provide the following configuration:
export default {
devServer: {
client: {
overlay: {
errors: true,
warnings: false,
runtimeErrors: true,
},
},
},
};
To filter based on the thrown error, you can pass a function that accepts an error parameter and returns a boolean.
For example, to ignore errors thrown by AbortController.abort():
export default {
devServer: {
client: {
overlay: {
runtimeErrors: (error) => {
if (error instanceof DOMException && error.name === 'AbortError') {
return false;
}
return true;
},
},
},
},
};
:::warning The function will not have access to the variables declared in the outer scope within the configuration file. :::
booleantruePrints compilation progress in percentage in the browser.
export default {
devServer: {
client: {
progress: true,
},
},
};
boolean | numbertrueTells dev server the number of times it should try to reconnect the client. When true it will try to reconnect unlimited times.
export default {
devServer: {
client: {
reconnect: true,
},
},
};
When set to false it will not try to reconnect.
export default {
devServer: {
client: {
reconnect: false,
},
},
};
You can also specify the exact number of times the client should try to reconnect.
export default {
devServer: {
client: {
reconnect: 5,
},
},
};
'ws' | stringwsThis option allows us to provide a custom client implementation. This allows specifying how the browser or other client communicates with the devServer.
To create a custom client implementation, create a class that extends BaseClient.
Using path to CustomClient.js, a custom WebSocket client implementation, along with the compatible 'ws' server:
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
export default {
devServer: {
client: {
webSocketTransport: require.resolve('./CustomClient'),
},
webSocketServer: 'ws',
},
};
Using custom, compatible WebSocket client and server implementations:
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
export default {
devServer: {
client: {
webSocketTransport: require.resolve('./CustomClient'),
},
webSocketServer: require.resolve('./CustomServer'),
},
};
:::tip When providing a custom client and server implementation make sure that they are compatible with one another to communicate successfully. :::
string | object{}This option allows specifying URL to web socket server (useful when you're proxying dev server and client script does not always know where to connect to).
export default {
devServer: {
client: {
webSocketURL: 'ws://0.0.0.0:8080/ws',
},
},
};
You can also specify an object with the following properties:
hostname: Tells clients connected to devServer to use the provided hostname.pathname: Tells clients connected to devServer to use the provided path to connect.password: Tells clients connected to devServer to use the provided password to authenticate.port: Tells clients connected to devServer to use the provided port.protocol: Tells clients connected to devServer to use the provided protocol.username: Tells clients connected to devServer to use the provided username to authenticate.export default {
devServer: {
client: {
webSocketURL: {
hostname: '0.0.0.0',
pathname: '/ws',
password: 'dev-server',
port: 8080,
protocol: 'ws',
username: 'rspack',
},
},
},
};
:::tip
To get protocol/hostname/port from browser use webSocketURL: 'auto://0.0.0.0:0/ws'.
:::
booleantrueEnable gzip compression for everything served:
export default {
devServer: {
compress: true,
},
};
object{}Provide options to @rspack/dev-middleware, which serves Rspack build assets from the dev server.
export default {
devServer: {
devMiddleware: {
index: true,
mimeTypes: { phtml: 'text/html' },
publicPath: '/publicPathForDevServe',
serverSideRender: true,
writeToDisk: true,
},
},
};
array | function | objectundefinedAdds headers to all responses:
export default {
devServer: {
headers: {
'X-Custom-Foo': 'bar',
},
},
};
You can also pass an array:
export default {
devServer: {
headers: [
{
key: 'X-Custom',
value: 'foo',
},
{
key: 'Y-Custom',
value: 'bar',
},
],
},
};
You can also pass a function:
export default {
devServer: {
headers: () => {
return { 'X-Bar': ['key1=value1', 'key2=value2'] };
},
},
};
boolean | objectfalseWhen using the HTML5 History API, the index.html page will likely have to be served in place of any 404 responses. Enable devServer.historyApiFallback by setting it to true:
export default {
devServer: {
historyApiFallback: true,
},
};
By providing an object this behavior can be controlled further using options like rewrites:
export default {
devServer: {
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html' },
{ from: /^\/subpage/, to: '/views/subpage.html' },
{ from: /./, to: '/views/404.html' },
],
},
},
};
When using dots in your path (common with Angular), you may need to use the disableDotRule:
export default {
devServer: {
historyApiFallback: {
disableDotRule: true,
},
},
};
For more options and information, see the connect-history-api-fallback documentation.
'local-ip' | 'local-ipv4' | 'local-ipv6' | string'local-ip'Specify a host to use. If you want your server to be accessible externally, specify it like this:
export default {
devServer: {
host: '0.0.0.0',
},
};
Specifying local-ip as host will try to resolve the host option as your local IPv4 address if available, if IPv4 is not available it will try to resolve your local IPv6 address.
Specifying local-ipv4 as host will try to resolve the host option as your local IPv4 address.
Specifying local-ipv6 as host will try to resolve the host option as your local IPv6 address.
boolean | 'only'trueEnable Rspack's hot module replacement feature:
export default {
devServer: {
hot: true,
},
};
To enable HMR without page refresh as a fallback in case of build failures, use hot: 'only':
export default {
devServer: {
hot: 'only',
},
};
booleantrueBy default, the dev server will reload/refresh the page when file changes are detected. devServer.hot option must be disabled or devServer.watchFiles option must be enabled in order for liveReload to take effect. Disable devServer.liveReload by setting it to false:
export default {
devServer: {
liveReload: false,
},
};
:::tip
Live reloading works only with web related targets like web, webworker, electron-renderer and node-webkit.
:::
function (devServer)Provides the ability to execute a custom function when @rspack/dev-server starts listening for connections on a port.
export default {
devServer: {
onListening: function (devServer) {
if (!devServer) {
throw new Error('@rspack/dev-server is not defined');
}
const port = devServer.server.address().port;
console.log('Listening on port:', port);
},
},
};
boolean | string | object | (string | object)[]trueTells dev server to open the browser after server had been started. Set it to true to open your default browser.
export default {
devServer: {
open: true,
},
};
:::info
When using rspack dev or rspack serve, you can override this option from the CLI with --open, --open <value>, or --no-open. When using rspack preview, this option is also respected, and --open takes precedence over devServer.open in the configuration.
:::
To open a specified page in a browser:
export default {
devServer: {
open: ['/my-page'],
},
};
To open multiple specified pages in browser:
export default {
devServer: {
open: ['/my-page', '/another-page'],
},
};
Provide browser name to use instead of the default one:
export default {
devServer: {
open: {
app: {
name: 'google-chrome',
},
},
},
};
The object accepts all open options:
export default {
devServer: {
open: {
target: ['first.html', 'http://localhost:8080/second.html'],
app: {
name: 'google-chrome',
arguments: ['--incognito', '--new-window'],
},
},
},
};
:::tip
The browser application name is platform-dependent. Don't hard code it in reusable modules. For example, 'Chrome' is 'Google Chrome' on macOS, 'google-chrome' on Linux, and 'chrome' on Windows.
:::
'auto' | string | number[]Specify a port number to listen for requests on:
export default {
devServer: {
port: 8080,
},
};
port option can't be null or an empty string, to automatically use a free port please use port: 'auto':
export default {
devServer: {
port: 'auto',
},
};
ProxyConfigArrayConfigure proxy rules for the dev server, and forward requests to the specified service.
This feature is powered by http-proxy-middleware. You can use all options provided by http-proxy-middleware.
Forward /api/* requests to https://example.com:
export default {
devServer: {
proxy: [
{
// http://localhost:8080/api -> https://example.com/api
// http://localhost:8080/api/users -> https://example.com/api/users
pathFilter: '/api',
target: 'https://example.com',
},
],
},
};
You can also proxy to a local service:
export default {
devServer: {
proxy: [
{
// http://localhost:8080/api -> http://localhost:3000/api
// http://localhost:8080/api/foo -> http://localhost:3000/api/foo
pathFilter: '/api',
target: 'http://localhost:3000',
},
],
},
};
Use pathRewrite to rewrite request paths, for example rewriting /foo to /bar on the target service:
export default {
devServer: {
proxy: [
{
// http://localhost:8080/foo -> https://example.com/bar
// http://localhost:8080/foo/baz -> https://example.com/bar/baz
pathFilter: '/foo',
target: 'https://example.com',
pathRewrite: { '^/foo': '/bar' },
},
],
},
};
pathFilter matches request paths for proxying, and supports strings, arrays, and functions. context is an alias of pathFilter.
export default {
devServer: {
proxy: [
{
// Only proxy /api traffic that is not an HTML request.
// http://localhost:8080/api/users -> https://example.com/api/users
pathFilter: (pathname, req) => {
const accept = req.headers.accept || '';
return pathname.startsWith('/api') && !accept.includes('html');
},
target: 'https://example.com',
},
],
},
};
'http' | 'https' | 'http2' | string | object'http'Allows to set server and options (by default 'http').
export default {
devServer: {
server: 'http',
},
};
To serve over HTTPS with a self-signed certificate:
export default {
devServer: {
server: 'https',
},
};
To serve over HTTP/2 with a self-signed certificate:
export default {
devServer: {
server: 'http2',
},
};
:::tip
If you use server: 'https' or server: 'http2' without providing server.options.key and server.options.cert, install selfsigned in your project so dev server can automatically generate a local certificate.
:::
Use the object syntax to provide your own certificate:
export default {
devServer: {
server: {
type: 'https',
options: {
ca: './path/to/server.pem',
pfx: './path/to/server.pfx',
key: './path/to/server.key',
cert: './path/to/server.crt',
passphrase: '@rspack/dev-server',
requestCert: true,
},
},
},
};
It also allows you to set additional TLS options like minVersion and you can directly pass the contents of respective files:
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default {
devServer: {
server: {
type: 'https',
options: {
minVersion: 'TLSv1.1',
key: fs.readFileSync(path.join(__dirname, './server.key')),
pfx: fs.readFileSync(path.join(__dirname, './server.pfx')),
cert: fs.readFileSync(path.join(__dirname, './server.crt')),
ca: fs.readFileSync(path.join(__dirname, './ca.pem')),
passphrase: '@rspack/dev-server',
requestCert: true,
},
},
},
};
const fs = require('node:fs');
const path = require('node:path');
module.exports = {
devServer: {
server: {
type: 'https',
options: {
minVersion: 'TLSv1.1',
key: fs.readFileSync(path.join(__dirname, './server.key')),
pfx: fs.readFileSync(path.join(__dirname, './server.pfx')),
cert: fs.readFileSync(path.join(__dirname, './server.crt')),
ca: fs.readFileSync(path.join(__dirname, './ca.pem')),
passphrase: '@rspack/dev-server',
requestCert: true,
},
},
},
};
function (middlewares, devServer)Provides the ability to execute a custom function and apply custom middleware(s).
export default {
devServer: {
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
throw new Error('@rspack/dev-server is not defined');
}
// Use the `unshift` method if you want to run a middleware before all other middlewares
// or when you are migrating from the `onBeforeSetupMiddleware` option
middlewares.unshift({
name: 'first-in-array',
// `path` is optional
path: '/foo/path',
middleware: (req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('Foo!');
},
});
// Use the `push` method if you want to run a middleware after all other middlewares
// or when you are migrating from the `onAfterSetupMiddleware` option
middlewares.push({
name: 'hello-world-test-one',
// `path` is optional
path: '/foo/bar',
middleware: (req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('Foo Bar!');
},
});
middlewares.push((req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('Hello World!');
});
return middlewares;
},
},
};
By default, @rspack/dev-server creates a connect-next app. If you need Express-only APIs, provide your own app explicitly:
import express from 'express';
export default {
devServer: {
app: async () => express(),
},
};
type DevServerStatic =
| string
| boolean
| DevServerStaticItem
| (string | DevServerStaticItem)[];
This option controls how the dev server serves static files from disk. These files are read directly from the file system and are not processed by Rspack.
By default, the public directory is served.
You can use the following forms:
boolean: false disables static file serving; true serves the default public directory with the default options.string: shorthand for static.directory.object: configure one static directory.array: configure multiple static directories.Set it to false to disable static file serving:
export default {
devServer: {
static: false,
},
};
When set to a string, it is equivalent to configuring static.directory:
export default {
devServer: {
static: 'assets',
},
};
To serve multiple static directories, pass an array:
export default {
devServer: {
static: ['assets', 'css'],
},
};
stringpath.join(process.cwd(), 'public')Tells the server which directory to read static files from, the value is an absolute path.
directory only controls which folder is read from disk. It does not decide where Rspack compilation assets are served from. The URL prefix for these static files is controlled by static.publicPath.
import path from 'node:path';
export default {
devServer: {
static: {
directory: path.join(import.meta.dirname, 'public'),
},
},
};
Provide an array of objects if you have multiple static folders:
import path from 'node:path';
export default {
devServer: {
static: [
{
directory: path.join(import.meta.dirname, 'assets'),
},
{
directory: path.join(import.meta.dirname, 'css'),
},
],
},
};
It is recommended to use an absolute path.
objectAllows configuring lower-level options for serving files. See the express.static documentation for the available options.
export default {
devServer: {
static: {
staticOptions: {
redirect: true,
},
},
},
};
string | string[]'/'Tells the server at which URL to serve the content from static.directory.
For example, to serve the file assets/manifest.json at /serve-public-path-url/manifest.json, configure it like this:
import path from 'node:path';
export default {
devServer: {
static: {
directory: path.join(import.meta.dirname, 'assets'),
publicPath: '/serve-public-path-url',
},
},
};
Provide an array of objects if you have multiple static folders with different URL prefixes:
import path from 'node:path';
export default {
devServer: {
static: [
{
directory: path.join(import.meta.dirname, 'assets'),
publicPath: '/serve-public-path-url',
},
{
directory: path.join(import.meta.dirname, 'css'),
publicPath: '/other-serve-public-path-url',
},
],
},
};
You can also mount the same static directory on multiple URL prefixes:
import path from 'node:path';
export default {
devServer: {
static: {
directory: path.join(import.meta.dirname, 'public'),
publicPath: ['/assets', '/shared-assets'],
},
},
};
boolean | objectControls whether files in static.directory are watched for changes. By default, changes to static files trigger a full page reload. Set it to false to disable watching.
import path from 'node:path';
export default {
devServer: {
static: {
directory: path.join(import.meta.dirname, 'public'),
watch: false,
},
},
};
You can also pass an object to configure advanced watch options. See the chokidar documentation for the available options.
import path from 'node:path';
export default {
devServer: {
static: {
directory: path.join(import.meta.dirname, 'public'),
watch: {
ignored: (filePath) => filePath.endsWith('.txt'),
usePolling: false,
},
},
},
};
string | string[] | object | (string | object)[]This option allows you to configure a list of directories/files to watch for file changes, implemented based on chokidar.
When files matched by watchFiles change, dev server triggers a full page refresh in the browser. This is mainly useful for watching files outside the Rspack's module graph, such as HTML templates or other non-bundled assets.
For example, watch a couple of directories and ignore everything except .php files:
export default {
devServer: {
watchFiles: {
paths: ['src', 'public'],
options: {
ignored: (filePath, stats) => {
if (!stats?.isFile()) {
return false;
}
return !filePath.endsWith('.php');
},
},
},
},
};
You can also provide an array when you need multiple entries:
export default {
devServer: {
watchFiles: [
'templates',
{
paths: 'public',
options: {
usePolling: false,
},
},
],
},
};
:::tip
chokidar documentation.devServer.liveReload is set to false, changes to these watched files will not refresh the browser.:::
boolean | 'ws' | string | objectThis option allows you to provide custom WebSocket server implementation.
The current default mode is 'ws'. This mode uses ws as a server, and native WebSockets on the client.
For example, using custom WebSocket client and server implementations:
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
export default {
devServer: {
client: {
webSocketTransport: require.resolve('./CustomClient'),
},
webSocketServer: require.resolve('./CustomServer'),
},
};