remix 3.0.0-beta.0 → 3.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/fetch-router.d.ts +7 -0
  2. package/dist/fetch-router.d.ts.map +1 -1
  3. package/dist/node-tsx/load-module.d.ts +2 -0
  4. package/dist/node-tsx/load-module.d.ts.map +1 -0
  5. package/dist/node-tsx/load-module.js +2 -0
  6. package/dist/node-tsx.d.ts +3 -0
  7. package/dist/node-tsx.d.ts.map +1 -0
  8. package/{src/node-serve.ts → dist/node-tsx.js} +2 -1
  9. package/dist/render-middleware.d.ts +2 -0
  10. package/dist/render-middleware.d.ts.map +1 -0
  11. package/dist/render-middleware.js +2 -0
  12. package/dist/route-pattern/href.d.ts +2 -0
  13. package/dist/route-pattern/href.d.ts.map +1 -0
  14. package/dist/route-pattern/href.js +2 -0
  15. package/dist/route-pattern/join.d.ts +2 -0
  16. package/dist/route-pattern/join.d.ts.map +1 -0
  17. package/dist/route-pattern/join.js +2 -0
  18. package/dist/route-pattern/match.d.ts +2 -0
  19. package/dist/route-pattern/match.d.ts.map +1 -0
  20. package/dist/route-pattern/match.js +2 -0
  21. package/package.json +158 -44
  22. package/src/assert/README.md +109 -0
  23. package/src/assets/README.md +539 -0
  24. package/src/async-context-middleware/README.md +100 -0
  25. package/src/auth/README.md +445 -0
  26. package/src/auth-middleware/README.md +246 -0
  27. package/src/cli/README.md +78 -0
  28. package/src/compression-middleware/README.md +176 -0
  29. package/src/cookie/README.md +106 -0
  30. package/src/cop-middleware/README.md +117 -0
  31. package/src/cors-middleware/README.md +174 -0
  32. package/src/csrf-middleware/README.md +99 -0
  33. package/src/data-schema/README.md +422 -0
  34. package/src/data-table/README.md +552 -0
  35. package/src/data-table-mysql/README.md +97 -0
  36. package/src/data-table-postgres/README.md +74 -0
  37. package/src/data-table-sqlite/README.md +84 -0
  38. package/src/fetch-proxy/README.md +46 -0
  39. package/src/fetch-router/README.md +902 -0
  40. package/src/fetch-router.ts +7 -0
  41. package/src/file-storage/README.md +57 -0
  42. package/src/file-storage-s3/README.md +47 -0
  43. package/src/form-data-middleware/README.md +109 -0
  44. package/src/form-data-parser/README.md +160 -0
  45. package/src/fs/README.md +60 -0
  46. package/src/headers/README.md +629 -0
  47. package/src/html-template/README.md +101 -0
  48. package/src/lazy-file/README.md +109 -0
  49. package/src/logger-middleware/README.md +132 -0
  50. package/src/method-override-middleware/README.md +71 -0
  51. package/src/mime/README.md +110 -0
  52. package/src/multipart-parser/README.md +241 -0
  53. package/src/node-fetch-server/README.md +352 -0
  54. package/src/node-tsx/README.md +79 -0
  55. package/src/node-tsx/load-module.ts +2 -0
  56. package/{dist/node-serve.js → src/node-tsx.ts} +2 -1
  57. package/src/render-middleware/README.md +99 -0
  58. package/src/render-middleware.ts +2 -0
  59. package/src/route-pattern/README.md +291 -0
  60. package/src/route-pattern/href.ts +2 -0
  61. package/src/route-pattern/join.ts +2 -0
  62. package/src/route-pattern/match.ts +2 -0
  63. package/src/session/README.md +171 -0
  64. package/src/session-middleware/README.md +109 -0
  65. package/src/session-storage-memcache/README.md +37 -0
  66. package/src/session-storage-redis/README.md +37 -0
  67. package/src/static-middleware/README.md +89 -0
  68. package/src/tar-parser/README.md +74 -0
  69. package/src/terminal/README.md +92 -0
  70. package/src/test/README.md +430 -0
  71. package/src/ui/README.md +219 -0
  72. package/src/ui/accordion/README.md +166 -0
  73. package/src/ui/anchor/README.md +153 -0
  74. package/src/ui/animation/README.md +316 -0
  75. package/src/ui/breadcrumbs/README.md +55 -0
  76. package/src/ui/button/README.md +44 -0
  77. package/src/ui/combobox/README.md +145 -0
  78. package/src/ui/glyph/README.md +72 -0
  79. package/src/ui/listbox/README.md +115 -0
  80. package/src/ui/menu/README.md +96 -0
  81. package/src/ui/popover/README.md +122 -0
  82. package/src/ui/scroll-lock/README.md +33 -0
  83. package/src/ui/select/README.md +107 -0
  84. package/src/ui/server/README.md +90 -0
  85. package/src/ui/test/README.md +107 -0
  86. package/src/ui/theme/README.md +103 -0
  87. package/dist/node-serve.d.ts +0 -2
  88. package/dist/node-serve.d.ts.map +0 -1
@@ -0,0 +1,78 @@
1
+ # cli
2
+
3
+ Command-line interface for creating and managing Remix projects.
4
+
5
+ ## Features
6
+
7
+ - Create new Remix projects with `npx remix@next new` or installed `remix new`
8
+ - Print shell completion scripts with `remix completion`
9
+ - Check project environment and Remix app conventions with `remix doctor`
10
+ - Create low-risk project and controller files with `remix doctor --fix`
11
+ - Inspect the current app route tree with `remix routes`
12
+ - Run project tests with `remix test`
13
+ - Print the current Remix version with `remix version`
14
+ - Use the same CLI through the `remix` package or the `remix/cli` API
15
+ - Scaffold a starter app that matches the Remix project layout conventions
16
+
17
+ ## Installation
18
+
19
+ Use `npx remix@next new <target-dir>` to scaffold a new Remix app. Install `remix` when you want the local `remix` command:
20
+
21
+ ```sh
22
+ npm i remix
23
+ ```
24
+
25
+ ## Shell completion
26
+
27
+ Install bash completion:
28
+
29
+ ```sh
30
+ remix completion bash >> ~/.bashrc
31
+ ```
32
+
33
+ Install zsh completion:
34
+
35
+ ```sh
36
+ remix completion zsh >> ~/.zshrc
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ Use `npx remix@next new my-remix-app` to scaffold a new Remix app. After installing Remix, the equivalent local command is `remix new my-remix-app`.
42
+
43
+ The rest of the CLI is available through the installed `remix` command:
44
+
45
+ ```sh
46
+ remix new my-remix-app
47
+ remix completion bash >> ~/.bashrc
48
+ remix doctor
49
+ remix doctor --fix
50
+ remix routes
51
+ remix routes --table
52
+ remix routes --table --no-headers
53
+ remix test
54
+ remix version
55
+ remix --no-color doctor
56
+ ```
57
+
58
+ You can also run the CLI programmatically:
59
+
60
+ ```ts
61
+ import { runRemix } from 'remix/cli'
62
+
63
+ await runRemix(['new', 'my-remix-app'])
64
+ await runRemix(['completion', 'bash'])
65
+ await runRemix(['doctor'])
66
+ await runRemix(['doctor', '--fix'])
67
+ await runRemix(['routes'])
68
+ await runRemix(['routes', '--table'])
69
+ await runRemix(['routes', '--table', '--no-headers'])
70
+ await runRemix(['test'])
71
+ await runRemix(['version'])
72
+ ```
73
+
74
+ `runRemix()` returns the CLI exit code as a promise.
75
+
76
+ ## License
77
+
78
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
@@ -0,0 +1,176 @@
1
+ # compression-middleware
2
+
3
+ Response compression middleware for Remix. It negotiates `br`, `gzip`, and `deflate` from `Accept-Encoding` and applies sensible defaults for when compression is useful.
4
+
5
+ ## Features
6
+
7
+ - **Encoding Negotiation** - Selects the best supported encoding from `Accept-Encoding`
8
+ - **Compression Guards** - Skips already-compressed responses and range-enabled responses
9
+ - **Size Thresholds** - Configurable minimum response size for compression
10
+ - **MIME Filtering** - Compresses only content types likely to benefit
11
+
12
+ ## Installation
13
+
14
+ ```sh
15
+ npm i remix
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```ts
21
+ import { createRouter } from 'remix/router'
22
+ import { compression } from 'remix/middleware/compression'
23
+
24
+ let router = createRouter({
25
+ middleware: [compression()],
26
+ })
27
+ ```
28
+
29
+ The middleware will automatically compress responses for compressible MIME types when:
30
+
31
+ - The client supports compression (`Accept-Encoding` header with a supported encoding)
32
+ - The response is large enough to benefit from compression (≥1024 bytes if `Content-Length` is present, by default)
33
+ - The response hasn't already been compressed
34
+ - The response doesn't advertise range support (`Accept-Ranges: bytes`)
35
+
36
+ ### Threshold
37
+
38
+ **Default:** `1024` (only enforced if `Content-Length` is present)
39
+
40
+ Set the minimum response size in bytes to compress:
41
+
42
+ ```ts
43
+ import { createRouter } from 'remix/router'
44
+ import { compression } from 'remix/middleware/compression'
45
+
46
+ let router = createRouter({
47
+ middleware: [
48
+ compression({
49
+ threshold: 2048, // Only compress responses ≥2KB
50
+ }),
51
+ ],
52
+ })
53
+ ```
54
+
55
+ ### Encodings
56
+
57
+ **Default:** `['br', 'gzip', 'deflate']`
58
+
59
+ Customize which compression algorithms to support:
60
+
61
+ ```ts
62
+ import { createRouter } from 'remix/router'
63
+ import { compression } from 'remix/middleware/compression'
64
+
65
+ let router = createRouter({
66
+ middleware: [
67
+ compression({
68
+ encodings: ['br', 'gzip'], // Only use Brotli and Gzip
69
+ }),
70
+ ],
71
+ })
72
+ ```
73
+
74
+ The `encodings` option can also be a function that receives the response:
75
+
76
+ ```ts
77
+ import { createRouter } from 'remix/router'
78
+ import { compression } from 'remix/middleware/compression'
79
+
80
+ let router = createRouter({
81
+ middleware: [
82
+ compression({
83
+ encodings: (response) => {
84
+ // Use different encodings for server-sent events
85
+ let contentType = response.headers.get('Content-Type')
86
+ return contentType?.startsWith('text/event-stream;')
87
+ ? ['gzip', 'deflate']
88
+ : ['br', 'gzip', 'deflate']
89
+ },
90
+ }),
91
+ ],
92
+ })
93
+ ```
94
+
95
+ ### Filter Media Type
96
+
97
+ **Default:** Uses `isCompressibleMimeType()` from [`remix/mime`](https://github.com/remix-run/remix/tree/main/packages/mime)
98
+
99
+ You can customize this behavior with the `filterMediaType` option:
100
+
101
+ ```ts
102
+ import { createRouter } from 'remix/router'
103
+ import { compression } from 'remix/middleware/compression'
104
+ import { isCompressibleMimeType } from 'remix/mime'
105
+
106
+ let router = createRouter({
107
+ middleware: [
108
+ compression({
109
+ filterMediaType(mediaType) {
110
+ // Add a custom media type to the default compressible list
111
+ return isCompressibleMimeType(mediaType) || mediaType === 'application/vnd.example+data'
112
+ },
113
+ }),
114
+ ],
115
+ })
116
+ ```
117
+
118
+ ### Compression Options
119
+
120
+ **Default:** Uses Node.js defaults for [zlib](https://nodejs.org/api/zlib.html#class-options) and [Brotli](https://nodejs.org/api/zlib.html#class-brotlioptions), with automatic flush handling for server-sent events.
121
+
122
+ You can pass options options to the underlying Node.js `zlib` and `brotli` compressors for fine-grained control:
123
+
124
+ ```ts
125
+ import { createRouter } from 'remix/router'
126
+ import { compression } from 'remix/middleware/compression'
127
+ import { zlib } from 'node:zlib'
128
+
129
+ let router = createRouter({
130
+ middleware: [
131
+ compression({
132
+ zlib: {
133
+ level: 6,
134
+ },
135
+ brotli: {
136
+ params: {
137
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 4,
138
+ },
139
+ },
140
+ }),
141
+ ],
142
+ })
143
+ ```
144
+
145
+ Like `encodings`, both `zlib` and `brotli` options can also be functions that receive the response:
146
+
147
+ ```ts
148
+ import zlib from 'node:zlib'
149
+ import { createRouter } from 'remix/router'
150
+ import { compression } from 'remix/middleware/compression'
151
+
152
+ let router = createRouter({
153
+ middleware: [
154
+ compression({
155
+ brotli: (response) => {
156
+ let contentType = response.headers.get('Content-Type')
157
+ return {
158
+ params: {
159
+ [zlib.constants.BROTLI_PARAM_QUALITY]: contentType?.startsWith('text/html;') ? 4 : 11,
160
+ },
161
+ }
162
+ },
163
+ }),
164
+ ],
165
+ })
166
+ ```
167
+
168
+ ## Related Packages
169
+
170
+ - [`remix/router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Router for the web Fetch API
171
+ - [`remix/mime`](https://github.com/remix-run/remix/tree/main/packages/mime) - MIME type utilities
172
+ - [`remix/response`](https://github.com/remix-run/remix/tree/main/packages/response) - Response helpers
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,106 @@
1
+ # cookie
2
+
3
+ Type-safe cookie parsing and serialization for Remix. It supports secure signing, secret rotation, and complete cookie attribute control.
4
+
5
+ ## Features
6
+
7
+ - **Secure Cookie Signing:** Built-in cryptographic signing using HMAC-SHA256 to prevent cookie tampering, with support for secret rotation without breaking existing cookies.
8
+ - **Secret Rotation Support:** Seamlessly rotate signing secrets while maintaining backward compatibility with existing cookies.
9
+ - **Web Standards Compliant:** Built on Web Crypto API and standard cookie parsing, making it runtime-agnostic (Node.js, Bun, Deno, Cloudflare Workers).
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm i remix
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```tsx
20
+ import { createCookie } from 'remix/cookie'
21
+
22
+ let sessionCookie = createCookie('session', {
23
+ httpOnly: true,
24
+ secrets: ['s3cret1'],
25
+ secure: true,
26
+ })
27
+
28
+ cookie.name // "session"
29
+ cookie.httpOnly // true
30
+ cookie.secure // true
31
+ cookie.signed // true
32
+
33
+ // Get the value of the "session" cookie from the request's `Cookie` header
34
+ let value = await sessionCookie.parse(request.headers.get('Cookie'))
35
+
36
+ // Set the value of the cookie in a Response's `Set-Cookie` header
37
+ let response = new Response('Hello, world!', {
38
+ headers: {
39
+ 'Set-Cookie': await sessionCookie.serialize(value),
40
+ },
41
+ })
42
+ ```
43
+
44
+ ### Signing Cookies
45
+
46
+ This library supports signing cookies, which is useful for ensuring the integrity of the cookie value and preventing tampering. Signing happens automatically when you provide a `secrets` option to the `Cookie` constructor.
47
+
48
+ Secret rotation is also supported, so you can easily rotate in new secrets without breaking existing cookies.
49
+
50
+ ```tsx
51
+ import { Cookie } from 'remix/cookie'
52
+
53
+ // Start with a single secret
54
+ let sessionCookie = createCookie('session', {
55
+ secrets: ['secret1'],
56
+ })
57
+
58
+ console.log(sessionCookie.signed) // true
59
+
60
+ let response = new Response('Hello, world!', {
61
+ headers: {
62
+ 'Set-Cookie': await sessionCookie.serialize(value),
63
+ },
64
+ })
65
+ ```
66
+
67
+ All cookies sent in this scenario will be signed with the secret `secret1`. Later, when it's time to rotate secrets, add a new secret to the beginning of the array and all existing cookies will still be able to be parsed.
68
+
69
+ ```tsx
70
+ let sessionCookie = createCookie('session', {
71
+ secrets: ['secret2', 'secret1'],
72
+ })
73
+
74
+ // This works for cookies signed with either secret
75
+ let value = await sessionCookie.parse(request.headers.get('Cookie'))
76
+
77
+ // Newly serialized cookies will be signed with the new secret
78
+ let response = new Response('Hello, world!', {
79
+ headers: {
80
+ 'Set-Cookie': await sessionCookie.serialize(value),
81
+ },
82
+ })
83
+ ```
84
+
85
+ ### Custom Encoding
86
+
87
+ By default, [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) and [`decodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent) are used to encode and decode the cookie value. This is suitable for most use cases, but you can provide your own functions to customize the encoding and decoding of the cookie value.
88
+
89
+ ```tsx
90
+ let sessionCookie = createCookie('session', {
91
+ encode: (value) => value,
92
+ decode: (value) => value,
93
+ })
94
+ ```
95
+
96
+ This can be useful for viewing the value of cookies in a human-readable format in the browser's developer tools. But you should be sure that the cookie value contains only characters that are [valid in a cookie value](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#attributes).
97
+
98
+ ## Related Packages
99
+
100
+ - [`headers`](https://github.com/remix-run/remix/tree/main/packages/headers) - Type-safe HTTP header manipulation
101
+ - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Build HTTP routers using the web fetch API
102
+ - [`node-fetch-server`](https://github.com/remix-run/remix/tree/main/packages/node-fetch-server) - Build HTTP servers on Node.js using the web fetch API
103
+
104
+ ## License
105
+
106
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
@@ -0,0 +1,117 @@
1
+ # cop-middleware
2
+
3
+ Cross-origin protection middleware for Remix. It mirrors Go's `CrossOriginProtection` by rejecting unsafe cross-origin browser requests without synchronizer tokens.
4
+
5
+ ## Features
6
+
7
+ - **Browser Provenance Checks** - Uses `Sec-Fetch-Site` when present and falls back to `Origin`
8
+ - **Trusted Origins** - Allow specific cross-origin callers by exact origin
9
+ - **Explicit Escape Hatches** - Support insecure bypass patterns for endpoints like webhooks
10
+ - **No Session State** - Does not require synchronizer tokens or server-side CSRF storage
11
+
12
+ ## Installation
13
+
14
+ ```sh
15
+ npm i remix
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```ts
21
+ import { createRouter } from 'remix/router'
22
+ import { cop } from 'remix/middleware/cop'
23
+
24
+ let router = createRouter({
25
+ middleware: [cop()],
26
+ })
27
+ ```
28
+
29
+ ## Behavior
30
+
31
+ For unsafe methods (`POST`, `PUT`, `PATCH`, `DELETE`), `cop()` follows the same broad model as Go's `CrossOriginProtection`:
32
+
33
+ - Allow `Sec-Fetch-Site: same-origin`
34
+ - Allow `Sec-Fetch-Site: none`
35
+ - Reject other `Sec-Fetch-Site` values unless the request matches a trusted origin or insecure bypass
36
+ - If `Sec-Fetch-Site` is missing, compare `Origin` to the request host
37
+ - If both `Sec-Fetch-Site` and `Origin` are missing, allow the request
38
+
39
+ This middleware is intentionally tokenless. If you cannot guarantee the deployment assumptions behind that model, prefer [`csrf-middleware`](https://github.com/remix-run/remix/tree/main/packages/csrf-middleware).
40
+
41
+ ## Caveats
42
+
43
+ - `cop()` is a browser-origin guard, not a universal CSRF solution. It is designed for deployments that can rely on modern browser provenance signals and same-origin request handling.
44
+ - If both `Sec-Fetch-Site` and `Origin` are missing on an unsafe request, `cop()` allows the request to continue. This is intentional so older clients and non-browser callers do not fail closed by default.
45
+ - If `Sec-Fetch-Site` is missing, `cop()` only rejects when `Origin` is present and does not match the request host.
46
+ - If you need stronger guarantees for session-backed form workflows, mixed deployment environments, or requests that should not fall through when browser provenance headers are missing, use [`csrf-middleware`](https://github.com/remix-run/remix/tree/main/packages/csrf-middleware) or layer both middlewares together.
47
+
48
+ ## Using with csrf-middleware
49
+
50
+ You can also layer `cop()` in front of `csrf()` when you want both browser provenance checks and session-backed synchronizer tokens.
51
+
52
+ ```ts
53
+ import { createCookie } from 'remix/cookie'
54
+ import { createRouter } from 'remix/router'
55
+ import { createCookieSessionStorage } from 'remix/session-storage/cookie'
56
+ import { session } from 'remix/middleware/session'
57
+ import { cop } from 'remix/middleware/cop'
58
+ import { csrf } from 'remix/middleware/csrf'
59
+
60
+ let sessionCookie = createCookie('__session', { secrets: ['secret1'] })
61
+ let sessionStorage = createCookieSessionStorage()
62
+
63
+ let router = createRouter({
64
+ middleware: [cop(), session(sessionCookie, sessionStorage), csrf()],
65
+ })
66
+ ```
67
+
68
+ In this setup, `cop()` runs first and rejects unsafe cross-origin browser requests early using `Sec-Fetch-Site` and `Origin`. Requests that pass `cop()` continue into `csrf()`, which still enforces synchronizer-token validation and origin checks for the remaining traffic.
69
+
70
+ ## Trusted Origins
71
+
72
+ ```ts
73
+ import { createRouter } from 'remix/router'
74
+ import { cop } from 'remix/middleware/cop'
75
+
76
+ let router = createRouter({
77
+ middleware: [
78
+ cop({
79
+ trustedOrigins: ['https://admin.example.com'],
80
+ }),
81
+ ],
82
+ })
83
+ ```
84
+
85
+ Trusted origins must be exact origin values in the form `scheme://host[:port]`.
86
+
87
+ ## Insecure Bypass Patterns
88
+
89
+ Bypass patterns intentionally weaken protection for specific endpoints. They support:
90
+
91
+ - Optional method prefixes, for example `POST /webhooks/{provider}`
92
+ - Exact paths, for example `/healthz`
93
+ - Trailing-slash subtree patterns, for example `/webhooks/`
94
+ - Single-segment wildcards with `{name}`
95
+ - Tail wildcards with `{name...}`
96
+
97
+ ```ts
98
+ import { createRouter } from 'remix/router'
99
+ import { cop } from 'remix/middleware/cop'
100
+
101
+ let router = createRouter({
102
+ middleware: [
103
+ cop({
104
+ insecureBypassPatterns: ['POST /webhooks/{provider}', '/healthz'],
105
+ }),
106
+ ],
107
+ })
108
+ ```
109
+
110
+ ## Related Packages
111
+
112
+ - [`csrf-middleware`](https://github.com/remix-run/remix/tree/main/packages/csrf-middleware) - Session-backed CSRF protection with synchronizer tokens
113
+ - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Router for the web Fetch API
114
+
115
+ ## License
116
+
117
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
@@ -0,0 +1,174 @@
1
+ # cors-middleware
2
+
3
+ CORS middleware for Remix. It adds standard CORS response headers to Fetch API servers and can either short-circuit preflight requests or pass them through to app-defined `OPTIONS` handlers.
4
+
5
+ ## Features
6
+
7
+ - **Preflight Handling** - Automatically handles `OPTIONS` preflight requests
8
+ - **Flexible Origin Rules** - Supports static, regex, list, and function-based origin policies
9
+ - **Credential Support** - Supports credentialed requests with spec-safe origin reflection
10
+ - **Header Controls** - Configure allowed and exposed headers, preflight methods, and max age
11
+ - **Private Network Support** - Optionally allow private network preflight requests
12
+
13
+ ## Installation
14
+
15
+ ```sh
16
+ npm i remix
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ts
22
+ import { createRouter } from 'remix/router'
23
+ import { cors } from 'remix/middleware/cors'
24
+
25
+ let router = createRouter({
26
+ middleware: [
27
+ cors({
28
+ origin: ['https://app.example.com', 'https://admin.example.com'],
29
+ credentials: true,
30
+ exposedHeaders: ['X-Request-Id'],
31
+ }),
32
+ ],
33
+ })
34
+
35
+ router.get('/api/projects', () => {
36
+ return Response.json([{ id: 'p1', name: 'Remix' }], {
37
+ headers: {
38
+ 'X-Request-Id': 'req_123',
39
+ },
40
+ })
41
+ })
42
+ ```
43
+
44
+ ## Origin Policies
45
+
46
+ `origin` supports:
47
+
48
+ - `'*'` to allow all origins
49
+ - `string` for a single exact origin
50
+ - `RegExp` for pattern-based matching
51
+ - `Array<string | RegExp>` for multiple exact and pattern matches
52
+ - `true` to reflect the request origin
53
+ - `(origin, context) => boolean | string` for dynamic policies
54
+
55
+ ### Restrict Origins
56
+
57
+ ```ts
58
+ let router = createRouter({
59
+ middleware: [
60
+ cors({
61
+ origin: ['https://app.example.com', 'https://admin.example.com'],
62
+ credentials: true,
63
+ }),
64
+ ],
65
+ })
66
+ ```
67
+
68
+ ### Dynamic Origin Policies
69
+
70
+ ```ts
71
+ let router = createRouter({
72
+ middleware: [
73
+ cors({
74
+ origin(origin, context) {
75
+ if (context.url.pathname.startsWith('/public/')) {
76
+ return '*'
77
+ }
78
+
79
+ return origin.endsWith('.trusted.example')
80
+ },
81
+ }),
82
+ ],
83
+ })
84
+ ```
85
+
86
+ ## Preflight Behavior
87
+
88
+ By default, preflight requests are short-circuited with status `204`.
89
+
90
+ ```ts
91
+ let router = createRouter({
92
+ middleware: [
93
+ cors({
94
+ methods: ['GET', 'POST', 'PATCH'],
95
+ allowedHeaders: ['Authorization', 'Content-Type'],
96
+ maxAge: 600,
97
+ }),
98
+ ],
99
+ })
100
+ ```
101
+
102
+ Use a function-based `allowedHeaders` policy when the header allowlist depends on the request:
103
+
104
+ ```ts
105
+ let router = createRouter({
106
+ middleware: [
107
+ cors({
108
+ allowedHeaders(request) {
109
+ let requestedHeaders = request.headers.get('Access-Control-Request-Headers')
110
+
111
+ if (requestedHeaders?.includes('x-admin-token')) {
112
+ return ['Authorization', 'Content-Type', 'X-Admin-Token']
113
+ }
114
+
115
+ return ['Authorization', 'Content-Type']
116
+ },
117
+ }),
118
+ ],
119
+ })
120
+ ```
121
+
122
+ Function-based `allowedHeaders` responses vary on `Access-Control-Request-Headers`, so caches do not reuse a preflight response for a different requested-header set.
123
+
124
+ Set `preflightContinue: true` to let downstream handlers process preflight requests. Use `preflightStatusCode` when you want short-circuited preflight responses to return a status other than `204`.
125
+
126
+ ## Private Network Preflights
127
+
128
+ ```ts
129
+ let router = createRouter({
130
+ middleware: [
131
+ cors({
132
+ allowPrivateNetwork: true,
133
+ }),
134
+ ],
135
+ })
136
+ ```
137
+
138
+ When `allowPrivateNetwork` is enabled, the middleware adds `Access-Control-Allow-Private-Network: true` for preflight requests that ask for private network access.
139
+
140
+ ## Expose Response Headers
141
+
142
+ ```ts
143
+ let router = createRouter({
144
+ middleware: [
145
+ cors({
146
+ exposedHeaders: ['X-Request-Id', 'X-Trace-Id'],
147
+ }),
148
+ ],
149
+ })
150
+ ```
151
+
152
+ ## Caveats
153
+
154
+ - CORS is primarily a browser enforcement mechanism. Disallowed non-preflight requests still reach your handlers unless you add separate request validation.
155
+ - When `credentials: true` is used with `origin: '*'`, the middleware reflects the request origin and adds `Vary: Origin` so the response stays cache-safe.
156
+ - When `allowedHeaders` is a function, preflight responses vary on `Access-Control-Request-Headers` so caches do not reuse a response for a different requested-header set.
157
+ - `preflightContinue` and `preflightStatusCode` only affect how preflight `OPTIONS` requests are handled. They do not change actual request authorization.
158
+
159
+ ## Related Packages
160
+
161
+ - [`cop-middleware`](https://github.com/remix-run/remix/tree/main/packages/cop-middleware) - Browser-origin protection middleware for unsafe cross-origin requests
162
+ - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Router for the web Fetch API
163
+ - [`headers`](https://github.com/remix-run/remix/tree/main/packages/headers) - Typed HTTP header utilities
164
+
165
+ ## Related Work
166
+
167
+ - [MDN: Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
168
+ - [Fetch Standard: CORS protocol](https://fetch.spec.whatwg.org/#http-cors-protocol)
169
+ - [expressjs/cors](https://github.com/expressjs/cors)
170
+ - [rack-cors](https://github.com/cyu/rack-cors)
171
+
172
+ ## License
173
+
174
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)