Skip to content

Commit 8fc84c5

Browse files
feat: add middleware routing (#146)
* refactor: create `src/build/functions/edge` directory * feat: remove lookaheads from matchers * feat: add routing logic to matchers * chore: add test * refactor: move things around and add test * refactor: remove check for Netlify Dev
1 parent 84b3a63 commit 8fc84c5

File tree

17 files changed

+1069
-13
lines changed

17 files changed

+1069
-13
lines changed

edge-runtime/lib/routing.ts

+451
Large diffs are not rendered by default.

edge-runtime/matchers.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

edge-runtime/middleware.ts

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
import type { Context } from '@netlify/edge-functions'
22

3+
import matchers from './matchers.json' assert { type: 'json' }
4+
35
import { buildNextRequest, RequestData } from './lib/next-request.ts'
46
import { buildResponse } from './lib/response.ts'
57
import { FetchEventResult } from './lib/response.ts'
8+
import {
9+
type MiddlewareRouteMatch,
10+
getMiddlewareRouteMatcher,
11+
searchParamsToUrlQuery,
12+
} from './lib/routing.ts'
613

714
type NextHandler = (params: { request: RequestData }) => Promise<FetchEventResult>
815

16+
const matchesMiddleware: MiddlewareRouteMatch = getMiddlewareRouteMatcher(matchers || [])
17+
918
/**
1019
* Runs a Next.js middleware as a Netlify Edge Function. It translates a web
1120
* platform Request into a NextRequest instance on the way in, and translates
@@ -20,13 +29,17 @@ export async function handleMiddleware(
2029
context: Context,
2130
nextHandler: NextHandler,
2231
) {
23-
// Don't run in dev
24-
if (Netlify.env.has('NETLIFY_DEV')) {
32+
const nextRequest = buildNextRequest(request, context)
33+
const url = new URL(request.url)
34+
35+
// While we have already checked the path when mapping to the edge function,
36+
// Next.js supports extra rules that we need to check here too, because we
37+
// might be running an edge function for a path we should not. If we find
38+
// that's the case, short-circuit the execution.
39+
if (!matchesMiddleware(url.pathname, request, searchParamsToUrlQuery(url.searchParams))) {
2540
return
2641
}
2742

28-
const nextRequest = buildNextRequest(request, context)
29-
3043
try {
3144
const result = await nextHandler({ request: nextRequest })
3245
const response = await buildResponse({ result, request: request, context })

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"url": "https://github.com/netlify/next-runtime-minimal/issues"
4040
},
4141
"homepage": "https://github.com/netlify/next-runtime-minimal#readme",
42-
"dependencies": {},
4342
"devDependencies": {
4443
"@fastly/http-compute-js": "1.1.1",
4544
"@netlify/blobs": "^6.4.0",

src/build/functions/edge.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,25 @@ const writeEdgeManifest = async (ctx: PluginContext, manifest: NetlifyManifest)
2323
await writeFile(join(ctx.edgeFunctionsDir, 'manifest.json'), JSON.stringify(manifest, null, 2))
2424
}
2525

26-
const writeHandlerFile = async (ctx: PluginContext, { name }: NextDefinition) => {
26+
const writeHandlerFile = async (ctx: PluginContext, { matchers, name }: NextDefinition) => {
2727
const handlerName = getHandlerName({ name })
28+
const handlerDirectory = join(ctx.edgeFunctionsDir, handlerName)
29+
const handlerRuntimeDirectory = join(handlerDirectory, 'edge-runtime')
2830

29-
await cp(
30-
join(ctx.pluginDir, 'edge-runtime'),
31-
join(ctx.edgeFunctionsDir, handlerName, 'edge-runtime'),
32-
{ recursive: true },
33-
)
31+
// Copying the runtime files. These are the compatibility layer between
32+
// Netlify Edge Functions and the Next.js edge runtime.
33+
await cp(join(ctx.pluginDir, 'edge-runtime'), handlerRuntimeDirectory, {
34+
recursive: true,
35+
})
36+
37+
// Writing a file with the matchers that should trigger this function. We'll
38+
// read this file from the function at runtime.
39+
await writeFile(join(handlerRuntimeDirectory, 'matchers.json'), JSON.stringify(matchers))
40+
41+
// Writing the function entry file. It wraps the middleware code with the
42+
// compatibility layer mentioned above.
3443
await writeFile(
35-
join(ctx.edgeFunctionsDir, handlerName, `${handlerName}.js`),
44+
join(handlerDirectory, `${handlerName}.js`),
3645
`
3746
import {handleMiddleware} from './edge-runtime/middleware.ts';
3847
import handler from './server/${name}.js';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const metadata = {
2+
title: 'Simple Next App',
3+
description: 'Description for Simple Next App',
4+
}
5+
6+
export default function RootLayout({ children }) {
7+
return (
8+
<html lang="en">
9+
<body>{children}</body>
10+
</html>
11+
)
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Page() {
2+
return (
3+
<main>
4+
<h1>Other</h1>
5+
</main>
6+
)
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Home() {
2+
return (
3+
<main>
4+
<h1>Home</h1>
5+
</main>
6+
)
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { headers } from 'next/headers'
2+
3+
export default function Page() {
4+
const headersList = headers()
5+
const message = headersList.get('x-hello-from-middleware-req')
6+
7+
return (
8+
<main>
9+
<h1>Message from middleware: {message}</h1>
10+
</main>
11+
)
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Redirect() {
2+
return (
3+
<main>
4+
<h1>If middleware works, we shoudn't get here</h1>
5+
</main>
6+
)
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Rewrite() {
2+
return (
3+
<main>
4+
<h1>If middleware works, we shoudn't get here</h1>
5+
</main>
6+
)
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { NextRequest } from 'next/server'
2+
import { NextResponse } from 'next/server'
3+
4+
export function middleware(request: NextRequest) {
5+
const response: NextResponse = NextResponse.next()
6+
7+
response.headers.set('x-hello-from-middleware-res', 'hello')
8+
9+
return response
10+
}
11+
12+
export const config = {
13+
matcher: [
14+
{
15+
source: '/foo',
16+
missing: [{ type: 'header', key: 'x-custom-header', value: 'custom-value' }],
17+
},
18+
],
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
output: 'standalone',
4+
eslint: {
5+
ignoreDuringBuilds: true,
6+
},
7+
}
8+
9+
module.exports = nextConfig

0 commit comments

Comments
 (0)