|
| 1 | +--- |
| 2 | +title: "Static File Plugin" |
| 3 | +description: "Serve static files alongside your procedures, with ETag caching, range requests, single page application fallback, and directory traversal protection." |
| 4 | +sidebar: |
| 5 | + label: "Static File" |
| 6 | +--- |
| 7 | + |
| 8 | +## Installation |
| 9 | + |
| 10 | +```package-install |
| 11 | +npm install @orpc/node@beta |
| 12 | +``` |
| 13 | + |
| 14 | +## Setup |
| 15 | + |
| 16 | +Use `StaticFileHandlerPlugin` to serve a directory alongside your procedures. Files are only served when no procedure matches a `GET` or `HEAD` request, so procedures always take precedence. |
| 17 | + |
| 18 | +```ts |
| 19 | +import { StaticFileHandlerPlugin } from '@orpc/node' |
| 20 | +import { RPCHandler } from '@orpc/server/node' |
| 21 | + |
| 22 | +const handler = new RPCHandler(router, { |
| 23 | + plugins: [ |
| 24 | + new StaticFileHandlerPlugin({ |
| 25 | + /** |
| 26 | + * The directory files are served from. Resolved against the working |
| 27 | + * directory when relative. |
| 28 | + */ |
| 29 | + rootDir: './public', |
| 30 | + |
| 31 | + /** |
| 32 | + * The URL path files are served under, appended to the handler prefix |
| 33 | + * when one is set. |
| 34 | + * |
| 35 | + * @default '/' |
| 36 | + */ |
| 37 | + path: '/', |
| 38 | + |
| 39 | + /** |
| 40 | + * The file served when the request path resolves to a directory. |
| 41 | + * Set to `false` to disable directory index files. |
| 42 | + * |
| 43 | + * @default 'index.html' |
| 44 | + */ |
| 45 | + indexFile: 'index.html', |
| 46 | + |
| 47 | + /** |
| 48 | + * A file served with status 200 when no file matches the request path, |
| 49 | + * relative to `rootDir`. Useful for single page application routing. |
| 50 | + * |
| 51 | + * @default undefined |
| 52 | + */ |
| 53 | + fallbackFile: 'index.html', |
| 54 | + |
| 55 | + /** |
| 56 | + * The `Cache-Control` response header value. Set to `false` to omit the header. |
| 57 | + * |
| 58 | + * @default 'public, max-age=0' |
| 59 | + */ |
| 60 | + cacheControl: 'public, max-age=0', |
| 61 | + |
| 62 | + /** |
| 63 | + * Whether files and directories whose name starts with a dot can be served. |
| 64 | + * |
| 65 | + * @default false |
| 66 | + */ |
| 67 | + dotfiles: false, |
| 68 | + |
| 69 | + /** |
| 70 | + * Whether precompressed `.br`, `.zst`, and `.gz` sidecar files can be served |
| 71 | + * when the client accepts their encoding. |
| 72 | + * |
| 73 | + * @default false |
| 74 | + */ |
| 75 | + precompressed: false, |
| 76 | + |
| 77 | + /** |
| 78 | + * Whether symbolic links whose target lies outside `rootDir` can be served. |
| 79 | + * Enabling this exposes every file those links reach. |
| 80 | + * |
| 81 | + * @default false |
| 82 | + */ |
| 83 | + allowSymlinks: false, |
| 84 | + |
| 85 | + /** |
| 86 | + * Extra content types keyed by lowercase file extension without the dot, |
| 87 | + * merged over the built-in detection. Unrecognised extensions are served |
| 88 | + * as `application/octet-stream`. |
| 89 | + */ |
| 90 | + mimeTypes: {}, |
| 91 | + }), |
| 92 | + ], |
| 93 | +}) |
| 94 | +``` |
| 95 | + |
| 96 | +Responses carry [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag) and [Last-Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Last-Modified) headers, so unchanged files revalidate as `304 Not Modified`, and [range requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Range_requests) are answered with `206 Partial Content` for media seeking and resumable downloads. |
| 97 | + |
| 98 | +:::info |
| 99 | +The `handler` can be any supported oRPC handler, such as [RPCHandler](/docs/rpc/handler), [OpenAPIHandler](/docs/openapi/handler), or a custom one. |
| 100 | +::: |
| 101 | + |
| 102 | +:::warning |
| 103 | +`rootDir` should contain only files you intend to make public. Requests cannot escape it through `..` segments or symbolic links, but every file inside it is reachable. |
| 104 | +::: |
| 105 | + |
| 106 | +## Compression |
| 107 | + |
| 108 | +Add the [Response Compression Plugin](/docs/plugins/response-compression) to compress files on the fly. It skips anything already encoded, so `precompressed` sidecars are served as they are, and it leaves `206 Partial Content` responses alone so range requests keep working. |
| 109 | + |
| 110 | +```ts |
| 111 | +import { ResponseCompressionHandlerPlugin } from '@orpc/server/plugins' |
| 112 | + |
| 113 | +const handler = new RPCHandler(router, { |
| 114 | + plugins: [ |
| 115 | + new StaticFileHandlerPlugin({ rootDir: './public', precompressed: true }), |
| 116 | + new ResponseCompressionHandlerPlugin(), |
| 117 | + ], |
| 118 | +}) |
| 119 | +``` |
| 120 | + |
| 121 | +:::tip |
| 122 | +Precompressing assets at build time costs nothing per request and compresses better than the on the fly pass, so reach for `precompressed` first and let the compression plugin cover whatever has no sidecar. |
| 123 | +::: |
| 124 | + |
| 125 | +## Learn More |
| 126 | + |
| 127 | +For implementation details, see the [source code](https://github.com/middleapi/orpc/blob/main/packages/node/src/static-file-handler-plugin.ts). |
0 commit comments