From c355bb3018c9e97d5831a2239189517c7e4bb501 Mon Sep 17 00:00:00 2001 From: Antoine Richard Date: Sat, 31 Aug 2024 00:55:29 +0200 Subject: [PATCH] feat: implement middleware module --- docs/markdown/07-middleware/00-title.md | 3 + docs/markdown/07-middleware/01-intro.md | 12 +++ docs/markdown/07-middleware/10-next-config.md | 9 +++ docs/markdown/07-middleware/11-headers.md | 81 +++++++++++++++++++ docs/markdown/07-middleware/12-matching.md | 26 ++++++ .../07-middleware/13-matching-attribute.md | 63 +++++++++++++++ docs/markdown/07-middleware/14-redirects.md | 22 +++++ docs/markdown/07-middleware/15-rewrite.md | 18 +++++ .../07-middleware/16-rewrite-advanced.md | 45 +++++++++++ docs/markdown/07-middleware/20-lab.md | 11 +++ docs/markdown/07-middleware/30-middleware.md | 9 +++ docs/markdown/07-middleware/31-definition.md | 28 +++++++ docs/markdown/07-middleware/32-use-cases.md | 9 +++ docs/markdown/07-middleware/33-convention.md | 14 ++++ .../markdown/07-middleware/34-url-matching.md | 28 +++++++ docs/markdown/07-middleware/35-api-modify.md | 54 +++++++++++++ .../07-middleware/36-api-rewrite-redirect.md | 29 +++++++ docs/markdown/07-middleware/37-api-return.md | 15 ++++ docs/markdown/07-middleware/38-warning.md | 11 +++ docs/markdown/07-middleware/40-lab.md | 11 +++ 20 files changed, 498 insertions(+) create mode 100644 docs/markdown/07-middleware/00-title.md create mode 100644 docs/markdown/07-middleware/01-intro.md create mode 100644 docs/markdown/07-middleware/10-next-config.md create mode 100644 docs/markdown/07-middleware/11-headers.md create mode 100644 docs/markdown/07-middleware/12-matching.md create mode 100644 docs/markdown/07-middleware/13-matching-attribute.md create mode 100644 docs/markdown/07-middleware/14-redirects.md create mode 100644 docs/markdown/07-middleware/15-rewrite.md create mode 100644 docs/markdown/07-middleware/16-rewrite-advanced.md create mode 100644 docs/markdown/07-middleware/20-lab.md create mode 100644 docs/markdown/07-middleware/30-middleware.md create mode 100644 docs/markdown/07-middleware/31-definition.md create mode 100644 docs/markdown/07-middleware/32-use-cases.md create mode 100644 docs/markdown/07-middleware/33-convention.md create mode 100644 docs/markdown/07-middleware/34-url-matching.md create mode 100644 docs/markdown/07-middleware/35-api-modify.md create mode 100644 docs/markdown/07-middleware/36-api-rewrite-redirect.md create mode 100644 docs/markdown/07-middleware/37-api-return.md create mode 100644 docs/markdown/07-middleware/38-warning.md create mode 100644 docs/markdown/07-middleware/40-lab.md diff --git a/docs/markdown/07-middleware/00-title.md b/docs/markdown/07-middleware/00-title.md new file mode 100644 index 0000000..7520035 --- /dev/null +++ b/docs/markdown/07-middleware/00-title.md @@ -0,0 +1,3 @@ + + +# Lifecycles & Middleware diff --git a/docs/markdown/07-middleware/01-intro.md b/docs/markdown/07-middleware/01-intro.md new file mode 100644 index 0000000..29930c5 --- /dev/null +++ b/docs/markdown/07-middleware/01-intro.md @@ -0,0 +1,12 @@ + + +# http request lifecycle on Next.js + +1. headers - next.config.js +2. redirects - next.config.js +3. middleware +4. rewrites | beforeFiles - next.config.js +5. routes (/public, /\_next/..., /app...) +6. rewrites | afterFiles - next.config.js +7. dynamic routes (/[slug]/page.tsx ...) +8. rewrites | fallback - next.config.js diff --git a/docs/markdown/07-middleware/10-next-config.md b/docs/markdown/07-middleware/10-next-config.md new file mode 100644 index 0000000..69e232e --- /dev/null +++ b/docs/markdown/07-middleware/10-next-config.md @@ -0,0 +1,9 @@ + + +# next.config.js + +**next.config.js** can be used to affect the request on various things : + +- headers +- rewrites +- redirects diff --git a/docs/markdown/07-middleware/11-headers.md b/docs/markdown/07-middleware/11-headers.md new file mode 100644 index 0000000..8b37b5a --- /dev/null +++ b/docs/markdown/07-middleware/11-headers.md @@ -0,0 +1,81 @@ + + +# next.config.js + +## headers + +```js +module.exports = { + async headers() { + return [ + { + source: '/employees', + headers: [ + { + key: 'x-some-custom-header', + value: 'hello im a custom header value', + }, + ], + }, + { + source: '/employees/:id(\\d{1,})', // will match /employees/123 (not /employees/abc) + headers: [ + { + key: 'x-custom-regex-header', + value: 'header value based on regex', + }, + ], + }, + ]; + }, +}; +``` + +##--## + +Advanced matching : + +```js +module.exports = { + async headers() { + return [ + { + source: '/:path*', // match everything except home + has: [ + { + type: 'header', + key: 'x-existing-header', // match based on existing header + }, + ], + headers: [ + { + key: 'x-another-header', + value: 'hello', + }, + ], + }, + { + source: '/employees', + has: [ + { + type: 'query', + key: 'search', + value: 'some-value', + }, + { + type: 'cookie', + key: 'authorized', + value: 'true', + }, + ], + headers: [ + { + key: 'x-authorized', + value: ':authorized', // reference value of cookie + }, + ], + }, + ]; + }, +}; +``` diff --git a/docs/markdown/07-middleware/12-matching.md b/docs/markdown/07-middleware/12-matching.md new file mode 100644 index 0000000..1959a51 --- /dev/null +++ b/docs/markdown/07-middleware/12-matching.md @@ -0,0 +1,26 @@ + + +# next.config.js + +## matching request + +Regex based matching : + +```js +module.exports = { + async headers() { + return [ + // Regex based matching : + { + source: '/employees/:id(\\d{1,})', // will match /employees/123 (not /employees/abc) + headers: [ + { + key: 'x-custom-regex-header', + value: 'header value based on regex', + }, + ], + }, + ]; + }, +}; +``` diff --git a/docs/markdown/07-middleware/13-matching-attribute.md b/docs/markdown/07-middleware/13-matching-attribute.md new file mode 100644 index 0000000..e8c332e --- /dev/null +++ b/docs/markdown/07-middleware/13-matching-attribute.md @@ -0,0 +1,63 @@ + + +# next.config.js + +## matching request + +Request attribute matching : + +- **has** : check if request has a specified attribute / value +- **missing** : check if request is missing attribute / value + +Has and missing objects can have the following fields : + +- type : header |cookie | host | query +- key +- value + +##--## + +```js +module.exports = { + async headers() { + return [ + { + source: '/:path*', // match everything except home + has: [ + { + type: 'header', + key: 'x-existing-header', // match based on existing header + }, + ], + headers: [ + { + key: 'x-another-header', + value: 'hello', + }, + ], + }, + { + source: '/employees', + has: [ + { + type: 'query', + key: 'search', + value: 'some-value', + }, + { + type: 'cookie', + key: 'authorized', + value: 'true', + }, + ], + headers: [ + { + key: 'x-authorized', + value: ':authorized', // reference value of cookie + }, + ], + }, + ]; + }, +}; +``` diff --git a/docs/markdown/07-middleware/14-redirects.md b/docs/markdown/07-middleware/14-redirects.md new file mode 100644 index 0000000..754eea7 --- /dev/null +++ b/docs/markdown/07-middleware/14-redirects.md @@ -0,0 +1,22 @@ + + +# next.config.js + +## redirects + +```js +module.exports = { + async redirects() { + return [ + { + source: '/about', + destination: '/', + permanent: true, + }, + ]; + }, +}; +``` + +- permanent **true** : redirects with 308 status code indicating caches to store the redirect permenatly +- permanent **false** : redirects with 307 status code (temporary, not cached) diff --git a/docs/markdown/07-middleware/15-rewrite.md b/docs/markdown/07-middleware/15-rewrite.md new file mode 100644 index 0000000..f6a18e0 --- /dev/null +++ b/docs/markdown/07-middleware/15-rewrite.md @@ -0,0 +1,18 @@ + + +# next.config.js + +## rewrites + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/about', + destination: '/', + }, + ]; + }, +}; +``` diff --git a/docs/markdown/07-middleware/16-rewrite-advanced.md b/docs/markdown/07-middleware/16-rewrite-advanced.md new file mode 100644 index 0000000..b46da18 --- /dev/null +++ b/docs/markdown/07-middleware/16-rewrite-advanced.md @@ -0,0 +1,45 @@ + + +# next.config.js + +## rewrites + +Detailed configuration : + +1. headers - next.config.js +2. redirects - next.config.js +3. middleware +4. rewrites | beforeFiles - next.config.js +5. routes (/public, /\_next/..., /app...) +6. rewrites | afterFiles - next.config.js +7. dynamic routes (/[slug]/page.tsx ...) +8. rewrites | fallback - next.config.js + +##--## + +```js +module.exports = { + async rewrites() { + return { + beforeFiles: [ + { + source: '/some-page', + destination: '/somewhere-else', + }, + ], + afterFiles: [ + { + source: '/non-existent', + destination: '/somewhere-else', + }, + ], + fallback: [ + { + source: '/:path*', + destination: `https://my-old-site.com/:path*`, + }, + ], + }; + }, +}; +``` diff --git a/docs/markdown/07-middleware/20-lab.md b/docs/markdown/07-middleware/20-lab.md new file mode 100644 index 0000000..b80d945 --- /dev/null +++ b/docs/markdown/07-middleware/20-lab.md @@ -0,0 +1,11 @@ + + +# next.config.js lifecycles + +## Lab + + + +TODO + + diff --git a/docs/markdown/07-middleware/30-middleware.md b/docs/markdown/07-middleware/30-middleware.md new file mode 100644 index 0000000..d91bd05 --- /dev/null +++ b/docs/markdown/07-middleware/30-middleware.md @@ -0,0 +1,9 @@ + + +# middleware + +Limits of next.config.js : + +- build time +- limited conditional rules +- not possible to return early diff --git a/docs/markdown/07-middleware/31-definition.md b/docs/markdown/07-middleware/31-definition.md new file mode 100644 index 0000000..7b38a51 --- /dev/null +++ b/docs/markdown/07-middleware/31-definition.md @@ -0,0 +1,28 @@ + + +# middleware + +What is next.js middleware ?
+ +allows to catch the incoming request and : + +- modify the request / response : + - rewrite + - redirect + - modify request headers + - modify response headers + - return early +- produce and return an early response + +##--## + +Runs before caching content / route matching : + +1. headers - next.config.js +2. redirects - next.config.js +3. **middleware** +4. rewrites | beforeFiles - next.config.js +5. routes (/public, /\_next/..., /app...) +6. rewrites | afterFiles - next.config.js +7. dynamic routes (/[slug]/page.tsx ...) +8. rewrites | fallback - next.config.js diff --git a/docs/markdown/07-middleware/32-use-cases.md b/docs/markdown/07-middleware/32-use-cases.md new file mode 100644 index 0000000..1f04fdd --- /dev/null +++ b/docs/markdown/07-middleware/32-use-cases.md @@ -0,0 +1,9 @@ + + +# middleware + +- Authentication +- Localization (ex : redirect user to the right locale) +- Bot detection +- Complex URL rewriting +- Logging / Analytics diff --git a/docs/markdown/07-middleware/33-convention.md b/docs/markdown/07-middleware/33-convention.md new file mode 100644 index 0000000..392ded1 --- /dev/null +++ b/docs/markdown/07-middleware/33-convention.md @@ -0,0 +1,14 @@ + + +# middleware + +## conventions + +Create a **/middleware.ts|js** file in the root directory
+export a **middleware** function : + +```js +export const middleware = (request) => { + // Some server operations on the request +}; +``` diff --git a/docs/markdown/07-middleware/34-url-matching.md b/docs/markdown/07-middleware/34-url-matching.md new file mode 100644 index 0000000..57208cb --- /dev/null +++ b/docs/markdown/07-middleware/34-url-matching.md @@ -0,0 +1,28 @@ + + +# request matching + +There are two ways to filter the requests :
+Exporting the **matcher** configuration : + +```js +export const config = { + matcher: ['/employee/:path*', '/expenses/:path*'], +}; +``` + +##--## + +Conditional statements : + +```js +export function middleware(request) { + if (request.nextUrl.pathname.startsWith('/employee')) { + // do something + } + + if (request.nextUrl.pathname.startsWith('/expenses')) { + // do something + } +} +``` diff --git a/docs/markdown/07-middleware/35-api-modify.md b/docs/markdown/07-middleware/35-api-modify.md new file mode 100644 index 0000000..23f0943 --- /dev/null +++ b/docs/markdown/07-middleware/35-api-modify.md @@ -0,0 +1,54 @@ + + +# API + +Modify response : + +```js +import { NextResponse } from 'next/server'; + +export function middleware(request) { + // Setting cookies on the response + const response = NextResponse.next(); + response.cookies.set('sfeir', 'nextjs'); + response.cookies.set({ + name: 'test', + value: 'hello-world', + path: '/', + }); + + // Setting headers on the response + response.headers.set('sfeir', 'nextjs'); + + return response; +} +``` + +##--## + +Modify request : + +```js +import { NextResponse } from 'next/server'; + +export function middleware(request) { + const cookie = request.cookies.get('nextjs'); + console.log(cookie); // => { name: 'nextjs', value: 'fast', Path: '/' } + + const allCookies = request.cookies.getAll(); + console.log(allCookies); // => [{ name: 'nextjs', value: 'fast' }] + + request.cookies.has('nextjs'); // => true + request.cookies.delete('nextjs'); + request.cookies.has('nextjs'); // => false + + // Setting headers + const requestHeaders = new Headers(request.headers); + requestHeaders.set('sfeir-request', 'nextjs'); + + // Setting cookies + request.cookies.set('sfeir-cookie', 'nextjs'); + + return response; +} +``` diff --git a/docs/markdown/07-middleware/36-api-rewrite-redirect.md b/docs/markdown/07-middleware/36-api-rewrite-redirect.md new file mode 100644 index 0000000..ca08bdf --- /dev/null +++ b/docs/markdown/07-middleware/36-api-rewrite-redirect.md @@ -0,0 +1,29 @@ + + +# API + +**Rewrite** response to display another URL : + +```js +import { NextResponse } from 'next/server'; + +export function middleware(request: NextRequest) { + if (request.nextUrl.pathname.startsWith('/employees')) { + return NextResponse.rewrite(new URL('/employees-internal', request.url)); + } +} +``` + +##--## + +**Redirect** the incoming request to a different URL : + +```js +import { NextResponse } from 'next/server'; + +export function middleware(request) { + if (request.nextUrl.pathname.startsWith('/employees')) { + return NextResponse.redirect(new URL('/', request.url)); + } +} +``` diff --git a/docs/markdown/07-middleware/37-api-return.md b/docs/markdown/07-middleware/37-api-return.md new file mode 100644 index 0000000..5d01cf2 --- /dev/null +++ b/docs/markdown/07-middleware/37-api-return.md @@ -0,0 +1,15 @@ + + +# API + +You can also produce a response early + +```js +export function middleware(request) { + if (request.nextUrl.pathname.startsWith('/employee')) { + if (!checkRequest(request)) { + return Response.json({ success: false, message: 'bad request' }, { status: 400 }); + } + } +} +``` diff --git a/docs/markdown/07-middleware/38-warning.md b/docs/markdown/07-middleware/38-warning.md new file mode 100644 index 0000000..5f15598 --- /dev/null +++ b/docs/markdown/07-middleware/38-warning.md @@ -0,0 +1,11 @@ + + +# Warning + +- Middleware is on the critical path : + + - avoid heavy operations & long tasks + +- Middleware does not run on a normal nodejs runtime : + - Native Node.js APIs are not supported (example : filesystem) + - only ES Modules node_modules are supported diff --git a/docs/markdown/07-middleware/40-lab.md b/docs/markdown/07-middleware/40-lab.md new file mode 100644 index 0000000..e233c22 --- /dev/null +++ b/docs/markdown/07-middleware/40-lab.md @@ -0,0 +1,11 @@ + + +# Middleware + +## Lab + + + +TODO + +