npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cxptools/next-app-middleware

v0.0.11

Published

WARNING: this is an experimental fork done for our own needs of using this in NextJS 14. It could get unmaintained, we might delete it, etc. Use at your own risk, we recommend forking the original repo instead.

Downloads

5

Readme

next-app-middleware

WARNING: this is an experimental fork done for our own needs of using this in NextJS 14. It could get unmaintained, we might delete it, etc. Use at your own risk, we recommend forking the original repo instead.

next.js uses your filesystem structure to route requests to pages, why should you have to manually route middleware?

Skip the task of matching request paths to conditionally run code in your middleware - this extension allows you to locate your middleware code directly inside the app directory and only run it when it matters.

before

/app/
 - /(authenticated)
   - /page1/page.js
   - /page2/page.js
 - /(unauthenticated)
   - /login/page.js
 - /page3/page.js
 - /page4/page.js
 - /layout.js
/middleware.js
// middleware.js
import isAuthenticated from "@/lib/isAuthenticated";
import { NextResponse } from "next/server";

const middleware = (req) => {
  // here every page that should be only accessed by authenticated users needs to be listed
  // and manually kept in sync with the project structure
  if (req.nextUrl.pathname === "/page1" || req.nextUrl.pathname === "/page2") {
    if (!(await isAuthenticated(req))) {
      return NextResponse.redirect("/login");
    }
  } else if (req.nextUrl.pathname === "/login") {
    if (await isAuthenticated(req)) {
      return NextResponse.redirect("/page1");
    }
  }
  return NextResponse.next();
};

export default middleare;

after

/app/
 - /(authenticated)
   - /page1/page.js
   - /page2/page.js
   - middleware.js
 - /(unauthenticated)
   - /login/page.js
   - middleware.js
 - /page3/page.js
 - /page4/page.js
 - /layout.js
// app/(authenticated)/middleware.js
import isAuthenticated from "@/lib/isAuthenticated";

const middleware = async (req) => {
  if (!(await isAuthenticated(req)))
    return {
      redirect: "/login",
    };
};

export default middleware;

// app/(unauthenticated)/middleware.js
import isAuthenticated from "@/lib/isAuthenticated";

const middleware = async (req) => {
  if (await isAuthenticated(req))
    return {
      redirect: "/page1",
    };
};

export default middleware;

In this setup the middleware will only run if the request path is matching a page in the (authenticated) group segment.

setup

install

pnpm

pnpm install next-app-middleware --save-dev

yarn

yarn add next-app-middleware --dev

npm

npm install next-app-middleware --save-dev

clean up current middleware and git working tree

  • delete your current middleware (or change the name if you want to keep it)
  • commit your repository
  • add /middleware.ts to your .gitignore
    • NOTE: include the / to not exclude any middleware.ts files in your app directory

next.config.js

const { withMiddleware } = require("next-app-middleware");

const nextConfig = {
  experimental: {
    appDir: true,
  },
};

module.exports = withMiddleware(nextConfig);

file conventions

NOTE: unless stated otherwise can be in any segment of the app directory.

middleware.{ts,js}

Middlewares will be called first when a request reaches its segment. A MiddlewareHandlerResult can be returned to intercept the request and stop the handler chain early. If the middleware returns void, execution continues normally.

const middleware: MiddlewareHandler = (req, res) => {
  let visitor_id = req.cookies.get("visitor_id")?.value;
  if (!visitor_id) {
    visitor_id = crypto.randomUUID();
    res.cookies.set("visitor_id", visitor_id);
  }
  req.waitUntil(trackVisit(visitor_id, req));
};

export default middleware;

forward.dynamic.{ts,js}

(Can not exist in route group segment)

Define internal path forwards in this file. Export named functions that indicate what parameter will be rewritten. Used to forward [dyanmic] segments.

/app/
  - [locale]
    - page.tsx
  - forward.dynamic.ts
// app/forward.dynamic.ts
export const locale = () => {
  return "en";
};

In this example the forward.dynamic.ts file declares a locale rewrite. This setup will result in the final middleware to consider any external request to / a request to /[locale] and will block all direct external requests to /[locale]

NOTE: If you return void from any of the forward functions, request routing will continue in the current segment.

/app/
  - [locale]
    - page.tsx
  - forward.dynamic.ts
  - page.tsx
// app/forward.dynamic.ts
export const locale = () => {
  return;
};

Here, the request will be routed to /app/page.tsx.

forward.static.{ts,js}

(Can not exist in route group segment)

Define internal path forwards in this file. Export named functions that indicate what parameter will be rewritten. Used to forward static segments.

/app/
  - hosted
    - page.tsx
  - forward.static.ts
// app/forward.static.ts
export const hosted = () => {
  return true;
};

In this example the forward.static.ts file declares a hosted rewrite. This setup will result in the final middleware to consider any external request to / a request to /hosted and will block all direct external requests to /hosted

NOTE: If you return false from any of the forward functions, request routing will continue in the current segment. (See above for an example)

external.{ts,js}

An external file allows routing traffic to other applications, default export should be either of type string or () => string | Promise<string>:

const origin = "https://example.com";

export default origin;
const getOrigin = async () => {
  return "http://localhost:3004";
};

export default getOrigin;

NOTE: Once an external file is found route collection is stopped, so any local pages in following segments will never be reached.

rewrite.{ts,js}

A rewrite file indicates to the framework that the directory is an external path. The rewrite handler will receive the same arguments as a middleware handler would but can returns the final rewirite path if the request should be rewritten.

redirect.{ts,js}

Similar to rewrite but results in a redirect instead of a rewrite

/**
 * both rewrites and redirects can co-exist with pages and can
 * dynamically choose to not perform any action and continue matching.
 * Priority: rewrite > redirect > page
 */

// /app/rewrite.ts
const rewrite: RewriteHandler = (req) => {
  if (req.cookies.get("rewrite")?.value === "true") return "/rewritten";
};

// /app/page.tsx
export default () => {
  return <></>;
};

export default rewrite;

/**
 * since there is both a rewrite and a page in the same directory
 * any request that does not have the `rewrite` cookie set to `true`
 * will fall through to the page file while the ones that do have the
 * cookie set will be rewritten to `/rewritten`
 * Note: if `void` is returned and no futher handler exist a `MatchingError`
 * is thrown.
 */

middleware.hooks.{ts,js}

A collection of hooks that can be used to extend the middleware lifecylce. Unlike others, this file has to be in the root of your project instead of the app directory.

notFound

This hook will be invoked if the middleware recieved a request that did not match any external page paths:

export const notFound: NotFoundHook = () => {
  // most hooks can return a NextResponse to override default behaviour
  return NextResponse(null, { status: 404 });
};

redirect

This hook will be invoked when a middleware returned a redirect response or a redirect file redirected the response:

export const redirect: RedirectHook = (_req, _res, destination, status) => {
  console.log(
    `Redirecting to ${destination} with status ${status)`
  );
}

rewrite

This hook will be invoked when a middleware returned a rewrite response or a rewrite file redirected the response:

export const rewrite: RewriteHook = (_req, _res, destination) => {
  console.log(`Rewriting to ${destination}`);
};

json

This hook will be invoked when a middleware resolved with a json response:

export const json: JsonHook = (_req, _res, data) => {
  return new NextResponse(yaml.stringfy(data), {
    headers: {
      "content-type": "application/x-yaml",
    },
  });
};

params

This hook can be used to override the path params before the final path is created:

export const params: ParamsHook = (params) => {
  if (params.test) {
    params.test = "override";
  }
  return params;
};

response

This hook will receive the final response and does not allow for editing it, it will be executed in the waitUntil method of the NextFetchEvent:

export const response: ResponseHook = (response) => {
  // collect metrics in here
};

error

Invoked when an error happens during matching or handler execution:

export const error: ErrorHook = (req, res, err) => {
  console.error(err);
  // the error will be re-thrown if this hook does not return a response,
  // causing a 500 response by default, if a response is returned, request
  // processing continues
  return new NextResponse(null, { status: 500 });
};