Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.0.8 #2

Merged
merged 2 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 123 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,90 +11,167 @@
</a>
</p>

# Introduction
`next-wayfinder` is a lightweight (_~3kb minzipped_) and flexible package that makes it easy to apply different Next.js
middlewares based on the route, without having to use cumbersome and error-prone path checks.
# Introduction

`next-wayfinder` is a lightweight (_~3kb minzipped_) and flexible package that makes it easy to apply different Next.js
middlewares based on the route, without having to use cumbersome and error-prone path checks.
This allows you to easily manage and maintain your middlewares, and keep your app clean and organized.

## Installation

```sh
npm install next-wayfinder
```

## Why
This package was created based on [this discussion][discussion-link].
In the discussion, a user highlighted the difficulty of handling complex routing inside the
## Why

This package was created based on [this discussion][discussion-link].
In the discussion, a user highlighted the difficulty of handling complex routing inside the
Next.js middleware. For instance, you might want to have a `withAuth` middleware only for paths matching `/dashboard/:path*` and an `i18n` middleware on a subdomain.
As of now, this can be achieved through ugly path checking inside a middleware that matches almost all the routes.
With `next-wayfinder` I aim to add some ease until Next officially supports multiple middleware for different matchers.


## Quick Start
`next-wayfinder` exports an `handlePaths` function that can be used as middleware entry point.

`next-wayfinder` exports an `handlePaths` function that can be used as middleware entry point.
It accepts an array of [`Middleware`](./src/types.ts) matching the route or the domain.

```ts
// middleware.ts

import { handlePaths } from 'next-wayfinder'
import { NextResponse } from 'next/server'
import { handlePaths } from "next-wayfinder";
import { NextResponse } from "next/server";

// the first matching middleware will be applied
export default handlePaths([
{
matcher: '/dashboard/:path*',
// additional filter
guard: params => params.lang === 'en',
handler: async req => {
// url params are injected by `handlePaths`
// in addition to req.query
// this is done because you might want to handle paths
// that are not available under your `app` or `pages` directory.
console.log(req.params)


// do some checks
if ( !isAuthenticated(req) ) {
return NextResponse.redirect('/')
}

// continue the request
return NextResponse.next()
export default handlePaths(
[
{
matcher: "/dashboard/:lang/:path*",
// We can filter this to apply only if some params matches exactly our needs
guard: params => params.lang === "en",
handler: async req => {
// url params are injected by `handlePaths`
// in addition to req.query
// this is done because you might want to handle paths
// that are not available under your `app` or `pages` directory.
console.log(req.params);

// do some checks
if (!isAuthenticated(req)) {
return NextResponse.redirect("/");
}

// continue the request
return NextResponse.next();
},
},
{
domain: /^app\./,
// rewrites all routes on domain app.* to the `pages/app/<path>`
handler: req =>
NextResponse.rewrite(
new URL("/app${req.nextUrl.pathname}", req.url)
),
},
{
// easy syntax for redirects
matcher: "/blog/:slug/edit",
// params will be replaced automatically
redirectTo: "/dashboard/posts/:slug",
},
],
{
debug: process.env.NODE_ENV !== "production",
// inject custom data, like session etc
// it will be available inside `req.injected`
injector: async request => ({ isLoggedIn: !!req.session }),
}
}, {
domain: /^app\./,
// rewrites all routes on domain app.* to the `pages/app/<path>`
handler: req => NextResponse.rewrite(new URL('/app${req.nextUrl.pathname}', req.url))
}
])
);
```

### What if I want to check paths on subdomain?

In some cases, you might want to check paths on a subdomain (i.e., using the same project for handling both the public website and the dashboard).
This can be easily achieved by passing an array of middleware as a handler. The `handlePaths` function iterates recursively over all the items provided (including nested ones), so a very high level of complexity can be "handled". However, to improve performance, I would recommend keeping it as simple as possible.

```ts
// middleware.ts

export default handlePaths([{
domain: /^app\./,
handler: [
export default handlePaths([
{
matcher: '/',
handler: req => NextResponse.redirect(new URL('/dashboard', req.url))
domain: /^app\./,
handler: [
{
matcher: "/",
handler: req =>
NextResponse.redirect(new URL("/dashboard", req.url)),
},
{
matcher: "/:path*",
handler: () => NextResponse.next(),
},
],
},
]);
```

### Example - Supabase Authentication

```ts
// middleware.ts
import { NextResponse, NextRequest } from "next/server";
import { handlePaths, NextREquestWithParams } from "next-wayfinder";
import { createMiddlewareSupabaseClient } from "@supabase/auth-helpers-nextjs";

const getSession = async (req: NextRequestWithParams) => {
const res = NextResponse.next();
const supabase = createMiddlewareSupabaseClient({ req, res });

const {
data: { session },
error,
} = await supabase.auth.getSession();

if (error) {
console.error(`Auth Error: `, error);
return null;
}

return session;
};

export default handlePaths(
[
{
// auth guard
matcher: "/dashboard/:path*",
pre: req =>
req.injected?.session ? true : { redirectTo: "/auth/sign-in" },
handler: req => {
console.log("User authenticated: ", req.injected?.session);

// do your stuff here

return NextResponse.next();
},
},
],
{
matcher: '/:path*',
handler: () => NextResponse.next(),
injector: async req => {
const session = await getSession(req);

return { session };
},
}
]
}])
);
```

## Authors

This library is created by Federico Vitale - ([@rawnly](https://github.com/rawnly))

## License
## License

The MIT License.

[discussion-link]: https://github.com/vercel/next.js/discussions/43816#discussioncomment-4348363
2 changes: 1 addition & 1 deletion __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { addParams, findMiddleware, getParams } from "../src/utils";
const queryForDomain = { domain: "app.acme.org", path: "/" };

const queryForPath = { domain: "", path: "/dashboard/it" };
const middlewares: Middleware[] = [
const middlewares: Middleware<unknown>[] = [
{
matcher: "/dashboard/:lang",
guard: params => params.lang === "en",
Expand Down
108 changes: 54 additions & 54 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,56 +1,56 @@
{
"name": "next-wayfinder",
"version": "0.0.7",
"description": "Apply multiple next.js middlewares with ease",
"main": "build/index.js",
"files": [
"package.json",
"build",
"README.md"
],
"types": "build/index.d.ts",
"license": "MIT",
"homepage": "https://github.com/Rawnly/next-wayfinder",
"keywords": [
"next",
"nextjs",
"middleware",
"auth",
"route"
],
"author": "Federico Vitale <mail@fedevitale.dev>",
"contributors": [],
"scripts": {
"coverage": "vitest run -c ./config/vitest.config.ts --coverage",
"test": "vitest -c ./config/vitest.config.ts",
"build": "tsup src --config ./config/tsup.config.ts",
"lint-fix": "eslint src/**/*.ts --fix",
"lint": "eslint src/**/*.ts",
"dev": "tsup src --watch --config ./config/tsup.config.ts",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@types/node": "^18.11.9",
"@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.42.0",
"@vitest/coverage-c8": "^0.24.5",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-unused-imports": "^2.0.0",
"next": "^13.0.6",
"prettier": "^2.7.1",
"tsup": "^6.3.0",
"typescript": "^4.8.4",
"vitest": "^0.24.5",
"vitest-fetch-mock": "^0.2.1"
},
"peerDependencies": {
"next": "^13.0.6"
},
"dependencies": {
"path-to-regexp": "^6.2.1",
"type-fest": "^3.3.0"
}
"name": "next-wayfinder",
"version": "0.0.8",
"description": "Apply multiple next.js middlewares with ease",
"main": "build/index.js",
"files": [
"package.json",
"build",
"README.md"
],
"types": "build/index.d.ts",
"license": "MIT",
"homepage": "https://github.com/Rawnly/next-wayfinder",
"keywords": [
"next",
"nextjs",
"middleware",
"auth",
"route"
],
"author": "Federico Vitale <mail@fedevitale.dev>",
"contributors": [],
"scripts": {
"coverage": "vitest run -c ./config/vitest.config.ts --coverage",
"test": "vitest -c ./config/vitest.config.ts",
"build": "tsup src --config ./config/tsup.config.ts",
"lint-fix": "eslint src/**/*.ts --fix",
"lint": "eslint src/**/*.ts",
"dev": "tsup src --watch --config ./config/tsup.config.ts",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@types/node": "^18.11.9",
"@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.42.0",
"@vitest/coverage-c8": "^0.24.5",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-unused-imports": "^2.0.0",
"next": "^13.0.6",
"prettier": "^2.7.1",
"tsup": "^6.3.0",
"typescript": "^4.8.4",
"vitest": "^0.24.5",
"vitest-fetch-mock": "^0.2.1"
},
"peerDependencies": {
"next": "^13.0.6"
},
"dependencies": {
"path-to-regexp": "^6.2.1",
"type-fest": "^3.3.0"
}
}
Loading