Skip to content

Commit fbe2bc9

Browse files
committed
fix: Update starters and docs to not use deprecated headers api (#652)
We introduced the awesome `response.headers` in #603, but we have not yet updated starters and docs to use it.
1 parent 8c8d3b2 commit fbe2bc9

File tree

9 files changed

+51
-43
lines changed

9 files changed

+51
-43
lines changed

docs/src/content/docs/core/routing.mdx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export default defineApp([
2828
---
2929
The `defineApp` function takes an array of middleware and route handlers that are executed in the order they are defined. In this example the request is passed through two middleware functions before being "matched" by the route handlers.
3030
---
31-
```
3231

3332
## Matching Patterns
3433

@@ -46,7 +45,6 @@ defineApp([
4645
1. The matching pattern string
4746
2. The request handler function
4847
---
49-
```
5048
5149
There are three matching patterns:
5250
@@ -103,7 +101,6 @@ Return values:
103101
- `Response`: A standard response object.
104102
- `JSX`: A React component, which is statically rendered to HTML on the server, streamed to the client, and then hydrated on the client side.
105103
---
106-
```
107104

108105
### Interrupters
109106

@@ -128,7 +125,6 @@ defineApp([
128125
---
129126
For the `/blog/:slug/edit` route, the `isAuthenticated` function will be executed first, if the user is not authenticated, the response will be a 401 Unauthorized. If the user is authenticated, the `EditBlogPage` component will be rendered. Therefore the flow is interrupted. The `isAuthenticated` function can be shared across multiple routes.
130127
---
131-
```
132128
133129
## Middleware & Context
134130
@@ -170,7 +166,6 @@ The context object:
170166
- if the user is authenticated the request will be passed to the next request handler and `"Hello {ctx.user.username}!"` will be returned
171167

172168
---
173-
```
174169

175170
## Documents
176171

@@ -194,7 +189,6 @@ This component will be rendered on the server side when the page loads. When def
194189
* Your application's stylesheets and scripts
195190
* A mount point for your page content (`id="root"` in the code below): this is where your actual page will be rendered - the "dynamic stuff" which updates using React Server Components.
196191
---
197-
```
198192
199193
```tsx title="src/pages/Document.tsx" mark={7}
200194
export const Document = ({ children }) => (
@@ -223,15 +217,32 @@ The `requestInfo` object is available in server functions and provides access to
223217
import { requestInfo } from "rwsdk/worker";
224218

225219
export async function myServerFunction() {
226-
const { request, headers, ctx } = requestInfo;
227-
// Use request, headers, or ctx as needed
220+
const { request, response, ctx } = requestInfo;
221+
// Use request, response, or ctx as needed
228222
}
229223
```
230224

231225
The `requestInfo` object contains:
232226

233-
- `request`: The incoming HTTP Request object
234-
- `headers`: Response headers
227+
- `request`: The incoming HTTP [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object
228+
- `response`: A [ResponseInit](https://fetch.spec.whatwg.org/#responseinit) object used to configure the status and headers of the response
235229
- `ctx`: The app context (same as what's passed to components)
236230
- `rw`: RedwoodSDK-specific context
237231
- `cf`: Cloudflare's Execution Context API
232+
233+
You can mutate the `response` object to configure the status and headers. For example:
234+
235+
```tsx
236+
import { route } from "rwsdk/router";
237+
238+
const NotFound = () => <div>Not Found</div>;
239+
240+
route('/some-resource', async ({ response }) => {
241+
// some logic to determine if the resource is not found
242+
243+
response.status = 404;
244+
response.headers.set('Cache-Control', 'no-store');
245+
246+
return <NotFound />;
247+
});
248+
```

docs/src/content/docs/reference/sdk-worker.mdx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,8 @@ export default defineApp([
8383

8484
The `requestInfo` object is used to get information about the current request. It's a singleton that's populated for each request, and constains the following information.
8585

86-
```ts
87-
import { requestInfo } from "rwsdk/worker";
88-
89-
requestInfo.request.url;
90-
requestInfo.request.method;
91-
requestInfo.request.body;
92-
requestInfo.ctx;
93-
requestInfo.headers;
94-
```
86+
- `request`: The incoming HTTP [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object
87+
- `response`: A [ResponseInit](https://fetch.spec.whatwg.org/#responseinit) object used to configure the status and headers of the response
88+
- `ctx`: The app context (same as what's passed to components)
89+
- `rw`: RedwoodSDK-specific context
90+
- `cf`: Cloudflare's Execution Context API

docs/src/content/docs/tutorial/full-stack-app/2-create-app.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -264,29 +264,29 @@ import { RouteMiddleware } from "rwsdk/router";
264264

265265
export const setCommonHeaders =
266266
(): RouteMiddleware =>
267-
({ headers, rw: { nonce } }) => {
267+
({ response, rw: { nonce } }) => {
268268
if (!import.meta.env.VITE_IS_DEV_SERVER) {
269269
// Forces browsers to always use HTTPS for a specified time period (2 years)
270-
headers.set(
270+
response.headers.set(
271271
"Strict-Transport-Security",
272272
"max-age=63072000; includeSubDomains; preload"
273273
);
274274
}
275275

276276
// Forces browser to use the declared content-type instead of trying to guess/sniff it
277-
headers.set("X-Content-Type-Options", "nosniff");
277+
response.headers.set("X-Content-Type-Options", "nosniff");
278278

279279
// Stops browsers from sending the referring webpage URL in HTTP headers
280-
headers.set("Referrer-Policy", "no-referrer");
280+
response.headers.set("Referrer-Policy", "no-referrer");
281281

282282
// Explicitly disables access to specific browser features/APIs
283-
headers.set(
283+
response.headers.set(
284284
"Permissions-Policy",
285285
"geolocation=(), microphone=(), camera=()"
286286
);
287287

288288
// Defines trusted sources for content loading and script execution:
289-
headers.set(
289+
response.headers.set(
290290
"Content-Security-Policy",
291291
`default-src 'self'; script-src 'self' 'nonce-${nonce}' https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; frame-src https://challenges.cloudflare.com; object-src 'none';`
292292
);

docs/src/content/docs/tutorial/full-stack-app/8-jobs-details.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ const Details = async ({ params }: RequestInfo) => {
203203
204204
<Aside type="tip" title="RequestInfo">
205205
The `RequestInfo` object contains:
206-
- `request`: The incoming HTTP Request object
207-
- `headers`: Response headers
206+
- `request`: The incoming HTTP [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object
207+
- `response`: A [ResponseInit](https://fetch.spec.whatwg.org/#responseinit) object used to configure the status and headers of the response
208208
- `ctx`: The app context
209209
210210
[You can find more information here.](/reference/sdk-worker/#requestinfo-requestinfo)

sdk/src/runtime/requestInfo/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ export interface RequestInfo<Params = any, AppContext = DefaultAppContext> {
1414
headers: Headers;
1515
rw: RwContext;
1616
cf: ExecutionContext;
17-
response: ResponseInit;
17+
// context(justinvdm, 2025-08-18): Ensure headers is always available
18+
response: ResponseInit & { headers: Headers };
1819
}

sdk/src/runtime/worker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export const defineApp = <
7171
pageRouteResolved: undefined,
7272
};
7373

74-
const userResponseInit: ResponseInit = {
74+
const userResponseInit: ResponseInit & { headers: Headers } = {
7575
status: 200,
7676
headers: new Headers(),
7777
};

starters/minimal/src/app/headers.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@ import { RouteMiddleware } from "rwsdk/router";
22

33
export const setCommonHeaders =
44
(): RouteMiddleware =>
5-
({ headers, rw: { nonce } }) => {
5+
({ response, rw: { nonce } }) => {
66
if (!import.meta.env.VITE_IS_DEV_SERVER) {
77
// Forces browsers to always use HTTPS for a specified time period (2 years)
8-
headers.set(
8+
response.headers.set(
99
"Strict-Transport-Security",
1010
"max-age=63072000; includeSubDomains; preload",
1111
);
1212
}
1313

1414
// Forces browser to use the declared content-type instead of trying to guess/sniff it
15-
headers.set("X-Content-Type-Options", "nosniff");
15+
response.headers.set("X-Content-Type-Options", "nosniff");
1616

1717
// Stops browsers from sending the referring webpage URL in HTTP headers
18-
headers.set("Referrer-Policy", "no-referrer");
18+
response.headers.set("Referrer-Policy", "no-referrer");
1919

2020
// Explicitly disables access to specific browser features/APIs
21-
headers.set(
21+
response.headers.set(
2222
"Permissions-Policy",
2323
"geolocation=(), microphone=(), camera=()",
2424
);
2525

2626
// Defines trusted sources for content loading and script execution:
27-
headers.set(
27+
response.headers.set(
2828
"Content-Security-Policy",
2929
`default-src 'self'; script-src 'self' 'nonce-${nonce}' https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; frame-src https://challenges.cloudflare.com; object-src 'none';`,
3030
);

starters/standard/src/app/headers.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@ import { RouteMiddleware } from "rwsdk/router";
22

33
export const setCommonHeaders =
44
(): RouteMiddleware =>
5-
({ headers, rw: { nonce } }) => {
5+
({ response, rw: { nonce } }) => {
66
if (!import.meta.env.VITE_IS_DEV_SERVER) {
77
// Forces browsers to always use HTTPS for a specified time period (2 years)
8-
headers.set(
8+
response.headers.set(
99
"Strict-Transport-Security",
1010
"max-age=63072000; includeSubDomains; preload",
1111
);
1212
}
1313

1414
// Forces browser to use the declared content-type instead of trying to guess/sniff it
15-
headers.set("X-Content-Type-Options", "nosniff");
15+
response.headers.set("X-Content-Type-Options", "nosniff");
1616

1717
// Stops browsers from sending the referring webpage URL in HTTP headers
18-
headers.set("Referrer-Policy", "no-referrer");
18+
response.headers.set("Referrer-Policy", "no-referrer");
1919

2020
// Explicitly disables access to specific browser features/APIs
21-
headers.set(
21+
response.headers.set(
2222
"Permissions-Policy",
2323
"geolocation=(), microphone=(), camera=()",
2424
);
2525

2626
// Defines trusted sources for content loading and script execution:
27-
headers.set(
27+
response.headers.set(
2828
"Content-Security-Policy",
2929
`default-src 'self'; script-src 'self' 'nonce-${nonce}' https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; frame-src https://challenges.cloudflare.com; object-src 'none';`,
3030
);

starters/standard/src/app/pages/user/functions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function getWebAuthnConfig(request: Request) {
2626

2727
export async function startPasskeyRegistration(username: string) {
2828
const { rpName, rpID } = getWebAuthnConfig(requestInfo.request);
29-
const { headers } = requestInfo;
29+
const { response } = requestInfo;
3030

3131
const options = await generateRegistrationOptions({
3232
rpName,
@@ -40,7 +40,7 @@ export async function startPasskeyRegistration(username: string) {
4040
},
4141
});
4242

43-
await sessions.save(headers, { challenge: options.challenge });
43+
await sessions.save(response.headers, { challenge: options.challenge });
4444

4545
return options;
4646
}

0 commit comments

Comments
 (0)