Skip to content

Commit 9f4044c

Browse files
committed
feat: add helper methods
1 parent 33ade90 commit 9f4044c

File tree

4 files changed

+79
-33
lines changed

4 files changed

+79
-33
lines changed

demos/middleware/middleware.ts

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,10 @@ export async function middleware(req: NextRequest) {
1616
const res = await request.next()
1717
const message = `This was static but has been transformed in ${req.geo.city}`
1818

19-
// Transform the response page data
20-
res.transformData((data) => {
21-
data.pageProps.message = message
22-
data.pageProps.showAd = true
23-
return data
24-
})
25-
26-
// Transform the response HTML
27-
res.rewriteHTML('p[id=message]', {
28-
text(textChunk) {
29-
if (textChunk.lastInTextNode) {
30-
textChunk.replace(message)
31-
} else {
32-
textChunk.remove()
33-
}
34-
},
35-
})
19+
// Transform the response HTML and props
20+
res.replaceText('p[id=message]', message)
21+
res.setPageProp('message', message)
22+
res.setPageProp('showAd', true)
3623

3724
return res
3825
}

demos/middleware/pages/static.js

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
1-
const Page = ({ message, showAd }) => (
2-
<div>
3-
<p id="message">{message}</p>
4-
{showAd ? (
5-
<div>
6-
<p>This is an ad that isn't shown by default</p>
7-
<img src="http://placekitten.com/400/300" />
8-
</div>
9-
) : (
10-
<p>No ads for me</p>
11-
)}
12-
</div>
13-
)
1+
import * as React from 'react'
142

15-
export async function getStaticProps(context) {
3+
const useHydrated = () => {
4+
const [hydrated, setHydrated] = React.useState(false)
5+
React.useEffect(() => {
6+
setHydrated(true)
7+
}, [])
8+
return hydrated
9+
}
10+
11+
const Page = ({ message, showAd }) => {
12+
const hydrated = useHydrated()
13+
return (
14+
<div>
15+
<p id="message">{message}</p>
16+
{hydrated && showAd ? (
17+
<div>
18+
<p>This is an ad that isn't shown by default</p>
19+
<img src="http://placekitten.com/400/300" />
20+
</div>
21+
) : (
22+
<p>No ads for me</p>
23+
)}
24+
</div>
25+
)
26+
}
27+
28+
export async function getStaticProps() {
1629
return {
1730
props: {
1831
message: 'This is a static page',

plugin/src/middleware/response.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NextResponse } from 'next/server'
33
import type { ElementHandlers } from './html-rewriter'
44

55
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6-
export type NextDataTransform = <T extends Record<string, any>>(props: T) => T
6+
export type NextDataTransform = <T extends { pageProps?: Record<string, any> }>(props: T) => T
77

88
// A NextReponse that wraps the Netlify origin response
99
// We can't pass it through directly, because Next disallows returning a response body
@@ -42,6 +42,52 @@ export class MiddlewareResponse extends NextResponse {
4242
this.elementHandlers.push([selector, handlers])
4343
}
4444

45+
/**
46+
* Sets the value of a page prop.
47+
* @see transformData if you need more control
48+
*/
49+
setPageProp(key: string, value: unknown) {
50+
this.transformData((props) => {
51+
props.pageProps ||= {}
52+
props.pageProps[key] = value
53+
return props
54+
})
55+
}
56+
57+
/**
58+
* Replace the text of the given element. Takes either a string or a function
59+
* that is passed the original string and returns new new string.
60+
* @see rewriteHTML for more control
61+
*/
62+
replaceText(selector: string, valueOrReplacer: string | ((input: string) => string)): void {
63+
// If it's a string then our job is simpler, because we don't need to collect the current text
64+
if (typeof valueOrReplacer === 'string') {
65+
this.rewriteHTML(selector, {
66+
text(textChunk) {
67+
if (textChunk.lastInTextNode) {
68+
textChunk.replace(valueOrReplacer)
69+
} else {
70+
textChunk.remove()
71+
}
72+
},
73+
})
74+
} else {
75+
let text = ''
76+
this.rewriteHTML(selector, {
77+
text(textChunk) {
78+
text += textChunk.text
79+
// We're finished, so we can replace the text
80+
if (textChunk.lastInTextNode) {
81+
textChunk.replace(valueOrReplacer(text))
82+
} else {
83+
// Remove the chunk, because we'll be adding it back later
84+
textChunk.remove()
85+
}
86+
},
87+
})
88+
}
89+
}
90+
4591
get headers(): Headers {
4692
// If we have the origin response, we should use its headers
4793
return this.originResponse?.headers || super.headers

plugin/src/templates/edge/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ export const buildResponse = async ({
7373
result.response = await result.response.next()
7474
}
7575
if (isMiddlewareResponse(result.response)) {
76+
const { response } = result
7677
if (request.method === 'HEAD' || request.method === 'OPTIONS') {
7778
return response.originResponse
7879
}
79-
const { response } = result
8080
// If it's JSON we don't need to use the rewriter, we can just parse it
8181
if (response.originResponse.headers.get('content-type')?.includes('application/json')) {
8282
const props = await response.originResponse.json()

0 commit comments

Comments
 (0)