nest-sirv
v0.1.1
Published
NestJS static assets module powered by sirv
Downloads
329
Maintainers
Readme
nest-sirv
NestJS static assets module powered by sirv — a drop-in alternative to @nestjs/serve-static for serving SPA frontends next to a high-RPS API.
Both this module and @nestjs/serve-static register their handler in onModuleInit, i.e. after the controller router. So a request that matches a controller route never reaches the static layer — the differences below apply to the traffic that does reach it: static assets, the SPA fallback, and unmatched paths.
Why
- No filesystem hit per request. In production mode sirv scans the directory once at startup and keeps file metadata (paths, sizes, headers, etags) in memory. Serving a file is a
Maplookup +createReadStream—express.staticdoes astat()syscall on every request it handles. - True route exclusion (also a safety boundary).
excludeguards the static handler itself: an excluded path costs a string comparison and is passed straight to the next handler. In@nestjs/serve-static,excludeonly suppresses the SPA fallback —express.staticstays mounted, so a file placed under an excluded prefix (e.g.public/api/secret.txt) is actually served to an unmatched request. - SPA fallback built in (
single: true) — no wildcard render handler, nopath-to-regexpcompiled per request. Note: sirv only falls back toindex.htmlfor paths that don't look like a file; a miss onsomething.jsreturns 404 rather than HTML (avoids soft-404s). Setsirv: { ignores: false }for the serve-static behaviour of falling back on every miss. - Precompressed assets. Put
bundle.js.br/bundle.js.gznext tobundle.jsand sirv serves them automatically based onAccept-Encoding.
Measured in isolation (bare http server, no Nest) against @nestjs/serve-static's express stack: ~+11% RPS on plain files, ~+32% on SPA fallback. In a real app the static layer only handles asset/SPA/unmatched traffic, so the end-to-end win is proportional to how much of that you serve.
Install
npm install nest-sirvRequires the Express platform adapter (@nestjs/platform-express).
Usage
import { Module } from '@nestjs/common';
import { SirvModule } from 'nest-sirv';
import { join } from 'node:path';
@Module({
imports: [
SirvModule.forRoot({
rootPath: join(__dirname, '..', 'frontend'),
exclude: ['/api'],
sirv: {
brotli: true,
gzip: true,
dev: process.env.NODE_ENV === 'development',
},
}),
],
})
export class AppModule {}Conditional registration (e.g. API-only instances) works the same way as with serve-static:
ConditionalModule.registerWhen(
SirvModule.forRoot({ rootPath, exclude: ['/api'] }),
() => process.env.DISABLE_FRONTEND !== 'true',
),forRootAsync with useFactory / useClass / useExisting is available as well.
Options
| Option | Type | Description |
| ----------- | -------------- | -------------------------------------------------------------------------------------------------------------- |
| rootPath | string | Required. Absolute path to the static assets directory. |
| serveRoot | string | URL prefix to mount under (default /). |
| exclude | string[] | Path prefixes that bypass static serving entirely. Matched on segment boundaries: '/api' excludes /api and /api/x, but not /api-docs. |
| sirv | sirv.Options | Passed through to sirv. Module defaults: etag: true, single: true, dev: false. |
See the sirv options reference for maxAge, immutable, brotli, gzip, extensions, dotfiles and friends.
Caveats
- Production mode snapshots the directory at startup. Files added after boot are not served until restart. This is exactly what you want for a frontend baked into a container image; set
sirv.dev = trueif the directory changes at runtime (local development). - Express adapter only. Fastify support is not implemented (the module throws a descriptive error).
License
MIT
