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

_middleware.js example without Next.js #50

Closed
remorses opened this issue Nov 25, 2021 · 43 comments
Closed

_middleware.js example without Next.js #50

remorses opened this issue Nov 25, 2021 · 43 comments

Comments

@remorses
Copy link

Is there an example of using _middlware.js wihout Nextjs?

My use case is to use _middlware.js as a reverse proxy, the problem of using nextjs is that _middlware.js won't reroute anything with path starting with /_next

@remorses remorses changed the title Example without nextjs _middlware.js example without nextjs Nov 25, 2021
@remorses remorses changed the title _middlware.js example without nextjs _middleware.js example without nextjs Nov 25, 2021
@leerob
Copy link
Member

leerob commented Nov 28, 2021

Hey! We’re working on adding some examples for this. Middleware does indeed work with any framework, or just vanilla HTML, by placing the _middleware file in your root directly (or for framework authors, using the File System API).

@leerob leerob changed the title _middleware.js example without nextjs _middleware.js example without Next.js Nov 28, 2021
@brycewray
Copy link

Am I correct in assuming this is unlikely to be addressed until 1Q 2022 due to the upcoming year-end holidays? Just curious. TIA for any such info.

@ptrhck
Copy link

ptrhck commented Jan 6, 2022

Any news on this topic?

@remorses
Copy link
Author

For anyone trying to use _middleware.js without next i am going to list some findings that may be useful:

  • you must have a package.json file on root too or it won't work.
  • Another cool thing to know is that you can also use _middleware.ts (using typescript) without any configuration because vercel bundles the _middleware file with esbuild on deployment
  • if you are trying to use NextResponse.rewrite from nextjs, you can use the following function for the same result
function rewrite(destination) {
    return new Response(null, {
        headers: {
            'x-middleware-rewrite':
                typeof destination === 'string'
                    ? destination
                    : destination.toString(),
        },
    })
}
  • For other cases just replace NextResponse with Response
  • To get req.nextUrl.pathname you can do new URL(req.url).pathname

@brycewray
Copy link

@remorses Although this doesn't eliminate the need for non-Next documentation from Vercel, it nonetheless is interesting and useful. Thanks!

@leerob
Copy link
Member

leerob commented Jan 13, 2022

We're still working on it - apologies for the delay.

@brycewray
Copy link

@remorses Just as a starter for those of us not all that into Next 😄 , how would you adjust the following _middleware.ts code from the fairly simple example, https://github.com/vercel/examples/tree/main/edge-functions/add-header ? In particular, the part of each of the examples that throws me is always the import statement in the top part, since that statement and the subsequent use of NextRequest both obviously depend on Next. To a Next-savvy person, a non-Next substitution probably seems super-easy, but a non-Next-savvy guy like me wouldn't have a clue where to go from there. Thanks in advance for any related thoughts you might have.

import type { NextRequest } from 'next/server'

export function middleware(req: NextRequest) {
  // You can add and append headers in multiple ways,
  // below we'll explore some common patterns

  // 1. Add a header to the `Headers` interface
  // https://developer.mozilla.org/en-US/docs/Web/API/Headers
  const headers = new Headers({ 'x-custom-1': 'value-1' })
  headers.set('x-custom-2', 'value-2')

  // 2. Add existing headers to a new `Response`
  const res = new Response(null, { headers })

  // 3. Add a header to an existing response
  res.headers.set('x-custom-3', 'value-3')

  // 4. Merge existing headers with new ones in a response
  return new Response(
    'Open the network tab in devtools to see the response headers',
    {
      headers: {
        ...Object.fromEntries(res.headers),
        'x-custom-4': 'value-4',
      },
    }
  )
}

@remorses
Copy link
Author

Instead of NextRequest you have to use Request, which is a javascript built in

To migrate to a simple request, you can see how NextRequest is implemented here

@ptrhck
Copy link

ptrhck commented Jan 17, 2022

@brycewray Would be great if you can share your code once implemented!

@brycewray
Copy link

@ptrhck I haven't yet been able to make anything (i.e., that works). While @remorses provided valuable info, there are just too many other items that I'm not savvy enough to "translate" from Next to non-Next. Sorry!

@dapptain
Copy link

dapptain commented Feb 9, 2022

Any update on documentation for non-nextjs projects? All the above is helpful, but something a bit more formal would be much appreciated.

@frandiox
Copy link

I'm always getting an error like:

TypeError: Cannot read property 'files' of undefined
--
21:41:41.309 | at /vercel/f053a0b194458027/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:26:255215

If I try to put _middleware.js at the root and run vercel, it shows that error. If I use the Filesytem API directly and create .output/functions-manifest.json and .output/server/pages/_middleware.js instead like you have in your source code and tests, it still throws.

The only way to bypass the error is moving .output/functions-manifest.json to .output/server/functions-manifest.json. This deploys but then nothing its returned from the function (404).

The code of the middleware is something like this:

"use strict";

const middleware = {
  default: function (request) {
    return new Response("hi from the edge!");
  },
};

_ENTRIES = typeof _ENTRIES === "undefined" ? {} : _ENTRIES;
_ENTRIES["middleware_pages/_middleware"] = {
  default: async function (ev) {
    const result = await middleware.default(ev.request, ev);
    return {
      promise: Promise.resolve(),
      waitUntil: Promise.resolve(),
      response:
        result ||
        new Response(null, {
          headers: {
            "x-middleware-next": 1,
          },
        }),
    };
  },
};

I've also added a ENABLE_FILE_SYSTEM_API=1 environment variable to the deployment but no luck.

Any quick pointer, @leerob ? Thanks.

@frandiox
Copy link

frandiox commented Feb 14, 2022

Alright, looks like it works when using middleware-manifest.json instead of functions-manifest.json, which is what `next build creates.

Now my problem is... To use the new ReadableStream() constructor, enable the streams_enable_constructors feature flag.. How does one enable this in an Edge Function without Wrangler? 🤯

@edcrampin
Copy link

Really would appreciate seeing some official docs detailing how to build middleware for a non-Next.js app. Managed to get a basic middleware working adapting @brycewray's snippet above to not use Next.js imports and saving this in my repo as ./_middleware.ts (copied below), but this prevents the actual app from rendering - I'd like to see an example of a middleware where I can run functionality (such as adding a custom header) but still serving my app, like the examples we have for Next.js apps.

export default function middleware(req: Request) {
  // You can add and append headers in multiple ways,
  // below we'll explore some common patterns

  // 1. Add a header to the `Headers` interface
  // https://developer.mozilla.org/en-US/docs/Web/API/Headers
  const headers = new Headers({ 'x-custom-1': 'value-1' })
  headers.set('x-custom-2', 'value-2')

  // 2. Add existing headers to a new `Response`
  const res = new Response(null, { headers })

  // 3. Add a header to an existing response
  res.headers.set('x-custom-3', 'value-3')

  // 4. Merge existing headers with new ones in a response
  return new Response(
    'Open the network tab in devtools to see the response headers',
    {
      headers: {
        ...Object.fromEntries(res.headers),
        'x-custom-4': 'value-4',
      },
    }
  )
}

@edcrampin
Copy link

edcrampin commented Feb 18, 2022

A bit of progress in the last half an hour from digging around in next/server at the NextResponse class - this is just an extension of the Response class in the same way NextRequest extends the Request class.

NextResponse.next() which is used to pass control to the next middleware function in the examples in this repo (or will serve your content if there is no more middleware in place) can be replicated by returning the below. Vercel must pick this header up and know to pass this to the next middleware.

return new Response(null, {
  headers: {
    'x-middleware-next': '1',
  },
});

You can do rewrites in a similar fashion, using a x-middleware-rewrite header and passing a URL as the value. For more info take a look at the NextRequest class in the Next.js repo.

One major issue I'm currently having is that if you write a blank middleware such as below, the middleware successfully runs and your app is 'served', however any assets (.css, .js) seem to throw a 404 when requested by your app. I'm not sure if this is something I've misconfigured, but this makes my setup essentially unusable. I can seem to access the file directly and the Vercel cache header reports a 'HIT', so not sure why I'm getting a 404 for these...

Another thing I'm having issues with is that even though Vercel's middleware are run through ESBuild, I cannot seem to use any ES6 functionality and have to write much more primitive JS. I can't use things such as Array.get or the Buffer class.

I've managed to convert the basic-auth-password middleware and get it sort of working despite the above, but I hit the issue with CSS/JS assets throwing 404s meaning I can't take it any further at the mo. My demo can be found here and the code for the _middleware.ts file I used is below.

export default function middleware(req: Request) {
  const basicAuth = req.headers['authorization'];

  if (basicAuth) {
    const auth = basicAuth.split(' ')[1]
    const decodedAuth = atob(auth).split(':');
    const user = decodedAuth[0];
    const pwd = decodedAuth[1];

    if (user === '4dmin' && pwd === 'testpwd123') {
      return new Response(null, {
        headers: {
          'x-middleware-next': '1',
        },
      })
    }
  }

  return new Response('Auth required', {
    status: 401,
    headers: {
      'WWW-Authenticate': 'Basic realm="Secure Area"',
    },
  })
}

@frandiox
Copy link

For anyone interested, I've made an example on how to deploy to Vercel Edge without Next.js: https://github.com/frandiox/hydrogen-vercel-edge
Demo: https://hydrogen-vercel-edge-frandiox.vercel.app/

As mentioned before, the key was using middleware-manifest.json instead of functions-manifest.json.
cc @leerob Perhaps this could be updated in the docs?

@brycewray
Copy link

For anyone interested, I've made an example on how to deploy to Vercel Edge without Next.js: https://github.com/frandiox/hydrogen-vercel-edge

Nice!

(But requires Shopify, right? I think what most of us are seeking are examples that require no specific platform but, rather, are as vanilla as possible.)

@edcrampin
Copy link

For anyone interested, I've made an example on how to deploy to Vercel Edge without Next.js: https://github.com/frandiox/hydrogen-vercel-edge Demo: https://hydrogen-vercel-edge-frandiox.vercel.app/

As mentioned before, the key was using middleware-manifest.json instead of functions-manifest.json. cc @leerob Perhaps this could be updated in the docs?

Thanks @frandiox, nice example using the File System API - that raises a good point that there are really two parts to this issue, I think what we need in this repo is an example of both:

  • Middleware using the File System API
  • Middleware with just a simple _middleware.js, with no need to play with the File System API

It seems like the second point is supported in Vercel, but perhaps not fully working/documented yet. As @brycewray mentions, I think this is what most people would be seeking so that the overhead of setting up Vercel middleware for a non-Nextjs project is as simple as possible.

@leerob
Copy link
Member

leerob commented Feb 25, 2022

I see you mentioned Hydrogen - what other frameworks are y'all wanting to use Middleware with? P.S. I'm still following along here 😄

@brycewray
Copy link

I see you mentioned Hydrogen - what other frameworks are y'all wanting to use Middleware with? P.S. I'm still following along here 😄

Can’t answer for others, @leerob, but I just want as plain-vanilla JS as possible, for converting an existing CF Worker to work on Vercel. Since Edge already uses CF Workers, seems it wouldn’t be that hard, although I readily confess that I’m only guessing about that.

@david-morris
Copy link

@leerob I'm using docusaurus, so I think that means static HTML.

@david-morris
Copy link

I'm encountering the following error on deployment with a _middleware.ts in the root of my docusaurus, and I'm wondering if anyone else has figured this one out:

TypeError: Unexpected MODIFIER at 1, expected END
    at f (/vercel/b5af1ed6dff7b1bc/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:468718)
    at parse (/vercel/b5af1ed6dff7b1bc/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:469358)
    at stringToRegexp (/vercel/b5af1ed6dff7b1bc/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:471777)
    at Object.pathToRegexp (/vercel/b5af1ed6dff7b1bc/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:472955)
    at sourceToRegex (/vercel/b5af1ed6dff7b1bc/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:175287)
    at /vercel/b5af1ed6dff7b1bc/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:173592
    at Array.map (<anonymous>)
    at convertRedirects (/vercel/b5af1ed6dff7b1bc/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:173560)
    at Object.build (/vercel/b5af1ed6dff7b1bc/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:31:195137)
    at async kot (/var/task/sandbox.js:197:12022)

@david-morris
Copy link

@edcrampin could you share the repo you have for converting the basic password auth? That would be a great example to work from. I'm having trouble understanding the various roots in the project, i.e., where the middleware should go.

@dapptain
Copy link

HELP! WE NEED DOCUMENTATION. ffs.....

@brycewray
Copy link

With the understanding that there may not be bandwidth at Vercel to reproduce all the examples in non-Next form, maybe @leerob et al. could take just one of the simpler examples — such as add-header or geolocation — and produce a non-Next form of it. It would be like a Rosetta Stone, showing that “Thing A gets done in Next projects like this (existing example) but in plain-vanilla JS it gets done like this (proposed example).” That could be really helpful and, perhaps, wouldn’t take too much time to convert from whichever Next project you select.

Just a thought. 🙂

@edcrampin
Copy link

@brycewray agreed, I've been planning to raise a PR to add something like this for a few basic examples using both the file system API and potentially also fully vanilla (single middleware file?) but struggling to get the time to look at this for now. The main nuisance to work around is how to organise middleware examples in this repo, and then also re-working the plop scripts to support this new structure.

I'd propose edge-functions would consist of three subdirectories, something like:

  • nextjs
  • vanilla
  • filesystem-api

...and moving all existing examples to the nextjs dir and starting to recreate some of the existing Next.js examples in the other subdirectories.

@brycewray
Copy link

brycewray commented Mar 18, 2022

@edcrampin That all sounds great! It’ll be tremendously appreciated — but I humbly re-suggest :) that, in the meantime and in view of the team’s absolutely understandable time limitations, the starting effort be something utterly simple, quick, and dirty, even if it’s only a vanilla version of _middleware.js for a given example but with everything else in that example left as-is.

@edcrampin
Copy link

@brycewray sounds completely sensible - I'll see what I can do.

@edcrampin
Copy link

@brycewray here's an example of an add-header and basic-auth middleware using create-react-app. The _middleware.ts file is compiled by ESBuild at build time within Vercel so having TypeScript does not matter despite being a non-TS project. These two examples are currently deployed here:

These mostly work but I'm coming across the same issue as I mentioned previously where certain assets return a 404 which I believe to be a bug within Vercel somewhere as certain assets load fine whilst others don't. Here's an example from the add-header example I included, you can see the custom headers in the response headers but the main.XXXX.js/css files are returning 404 so the React app does not load. @leerob is this something you're aware of at all?

image

@brycewray
Copy link

@edcrampin Thanks very much! Will stand by for @leerob to weigh in on your question before trying.

@david-morris
Copy link

Thanks for the example, @edcrampin ! I feel that I've been able to rule out issues with the _middleware file and package.json thanks to that repo.

However, there appears to be some remaining issue, and I don't understand where to start analyzing this issue. It appears that moving the _middleware file will switch this error between being a simple middleware and a filesystem API issue, but there's something that the edge function deployer just doesn't like about ...something? My repo? My framework? My account?

As an aside, it would be nice to be able to test these things without waiting for a remote deployment and committing.

I'd love to hear input on what could be related to this issue, and I'm interested in helping to make sure that the documentation can help people who are using whatever new technology, and want to add middleware.

TypeError: Unexpected MODIFIER at 1, expected END
    at parse (/vercel/b8b564fa9bd13b57/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:539986)
    at stringToRegexp (/vercel/b8b564fa9bd13b57/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:542405)
    at Object.pathToRegexp (/vercel/b8b564fa9bd13b57/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:543583)
    at sourceToRegex (/vercel/b8b564fa9bd13b57/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:224129)
    at /vercel/b8b564fa9bd13b57/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:222434
    at Array.map (<anonymous>)
    at convertRedirects (/vercel/b8b564fa9bd13b57/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:1:222402)
    at Object.build (/vercel/b8b564fa9bd13b57/.build-utils/.builder/node_modules/@vercelruntimes/file-system-api/dist/index.js:31:90032)
    at async qot (/var/task/sandbox.js:197:12762)

@charlax
Copy link

charlax commented Mar 24, 2022

I'm also using the latest docusaurus and getting the same error TypeError: Unexpected MODIFIER at 1, expected END.

I tried with a noop middleware and it still fails, most probably related to docusaurus:

export default function middleware(req: Request) {
  return new Response(null, {
    headers: {
      "x-middleware-next": "1",
    },
  });
}

@david-morris
Copy link

@edcrampin , could you share more about the settings you're using to deploy your example?

I assumed that it would use either the Other or Create React App preset, with the root directory edge-functions/create-react-app/basic-auth (for the basic auth example), and no other settings changed.

I've tried deploying it and it failed:

Cloning bitbucket.org/Nefino/vercel-examples (Branch: main, Commit: 540430d)
Cloning completed: 1.921s
Installing build runtime...
Build runtime installed: 4.358s
Looking up build cache...
Build Cache not found
Detected package.json
Installing dependencies...
Detected `package-lock.json` generated by npm 7...
npm WARN deprecated [email protected]: This SVGO version is no longer supported. Upgrade to v2.x.x.
added 1369 packages in 45s
169 packages are looking for funding
  run `npm fund` for details
Running `vercel build`
Vercel CLI 23.1.3-canary.71 build (beta) — https://vercel.com/feedback
Retrieved Project Settings:
  - Framework Preset: Create React App
  - Build Command: `npm run build` or `react-scripts build`
  - Output Directory: build
Loaded 2 CLI Plugins
Running Build Command: `npm run build`
> [email protected] build
> react-scripts build && cp _middleware.ts ./build/
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
  42.77 kB  build/static/js/main.3dba3da1.js
  541 B     build/static/css/main.073c9b0a.css
The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
You may serve it with a static server:
  npm install -g serve
  serve -s build
Find out more about deployment here:
  https://cra.link/deployment
Copying files from "build" to ".output/static"
Copied 14 files from "build" to ".output/static" [38ms]
Running 2 CLI Plugins after Build Command:
  > vercel-plugin-middleware.build: Compiling middleware file: "/vercel/path0/edge-functions/create-react-app/basic-auth/_middleware.ts"
Build Completed in .output [12s]
Uploading build outputs...
An unexpected internal error occurred

@ptrhck
Copy link

ptrhck commented Apr 1, 2022

I was also trying to deploy the example and now I am even getting the following:
MicrosoftTeams-image
So, I am really wondering what the current status is here?

@charlax
Copy link

charlax commented Apr 1, 2022

Btw, I got the following response from Vercel support:

Edge Functions (beta) are not ready outside of Next.js at the moment. We are working on making this possible!

So I guess we just have to be patient :)

@red2678
Copy link

red2678 commented Apr 10, 2022

I feel like you guys putting examples of middleware implementations on your site, saying you work with all frameworks, only to not, is blatantly false advertising.

image

image

Nowhere on these pages does it say that middleware is only supported by Next.js apps.

https://vercel.com/features/edge-functions

https://vercel.com/docs/concepts/functions/edge-functions

Riiiiiiiight......

image

@leerob
Copy link
Member

leerob commented Jul 6, 2022

Forgot to swing back here, but with the stable release of Middleware, it can now be used with all frameworks 😄

Will be adding more examples, as well! This is just the start.

https://vercel.com/docs/concepts/functions/edge-middleware/quickstart

@leerob leerob closed this as completed Jul 6, 2022
bfabio added a commit to teamdigitale/innovazione.gov.it-site that referenced this issue Jul 11, 2022
Vercel Middleware stabilized
(vercel/examples#50 (comment)),
re-add the password protection for staging environments.

See 7728506.

This reverts commit 8f01e6e.
@brycewray
Copy link

brycewray commented Mar 12, 2023

I did finally get it working on a Hugo repo:
https://github.com/brycewray/hugo-site/blob/main/middleware.js
https://github.com/brycewray/hugo-site/blob/main/.deprecated/middleware.js

While most of its headers-handling could’ve been done in vercel.json, the Content Security Policy needed dynamically generated nonces scattered throughout the site at appropriate places (scripts, certain links, etc.). This handles that just as its Cloudflare Worker predecessor did in Cloudflare Pages. 👍

@leerob, my compliments to your team for the content of both the Edge Functions and Middleware sections in the documentation — the info is much more comprehensive, and considerably easier to follow, than the last time I checked.

@dmarkbreiter
Copy link

@david-morris @charlax did you ever get your docusaurus sites behind authentication?

@david-morris
Copy link

@dmarkbreiter yes, I think we just crammed Docusaurus behind a Next.js shim before this was fixed though.

@Youhan
Copy link

Youhan commented Dec 21, 2023

Is there a Nuxt example out there?

@levino
Copy link

levino commented Mar 5, 2024

Here is my middleware for a docusaurus static site:

const auth = process.env.AUTH

export default function middleware(request) {
  const basicAuth = request.headers.get('Authorization')
  if (!basicAuth) {
    return new Response('Access denied', {
      status: 401,
      headers: { 'WWW-Authenticate': 'Basic' },
    })
  }
  const authValue = basicAuth.split(' ')[1]
  if (authValue !== auth) {
    return new Response('Access denied', {
      status: 401,
      headers: { 'WWW-Authenticate': 'Basic' },
    })
  }
}

Use

echo -n "username:password" | base64

to generate the AUTH string.

@jkmartindale
Copy link

Since this is still the easiest resource to find for using Vercel middleware without Next, here's a tip I wish I had known earlier.

@vercel/edge gives you rewrite()/next() like NextResponse but without the headache of loading all of Next just for middleware. This is my simple middleware for disabling direct file access on a project that's just serverless functions, which is another annoying Vercel default but whatever:

import { rewrite } from "@vercel/edge"

export const config = {
    matcher: "/((?!api|static).*)",
}

export default function middleware(_request: Request) {
    return rewrite("/404")
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests