I thought this would be a simple task, but I struggle to find a way to force my webpage to use https. The next.js webapp lives on the heroku servers and I've set up the SSL. Both https and http version works, but how to I force or redirect the website to use the https version. I've seen some solution using express, but nothing in my webapp are using express, is it required? Thanks.
Asked
Active
Viewed 1.1k times
2 Answers
25
As of Nextjs v12, you can use middleware instead of a setting up a custom server.
Middleware is a better solution for the following reasons:
- a custom server often requires additional dependencies (like express)
- you give up some of the box features like automatic static optimization
- Middleware can be scope to specific paths using the built in routing paradigm
Create a /pages/_middleware.ts
(or .js
) file with something similar to this:
import { NextFetchEvent, NextRequest, NextResponse } from 'next/server'
type Environment = "production" | "development" | "other";
export function middleware(req: NextRequest, ev: NextFetchEvent) {
const currentEnv = process.env.NODE_ENV as Environment;
if (currentEnv === 'production' &&
req.headers.get("x-forwarded-proto") !== "https") {
return NextResponse.redirect(
`https://${req.headers.get('host')}${req.nextUrl.pathname}`,
301
);
}
return NextResponse.next();
}
I also created an npm package for this.
import sslRedirect from 'next-ssl-redirect-middleware';
export default sslRedirect({});
-
1Problem with this is, where ever you deploy this code, req.nextUrl.hostname will always resolve to the localhost. Because server internally runs on localhost. http://example.com/page-1 ===> https://localhost/page-1 – Lakshit Nagar Jan 20 '22 at 21:20
-
@LakshitNagar The `req.nextUrl` should contain the request url from the client perspective. I can confirm this is how it works in heroku (i'm actively using this package and it works). Although there is some odd behavior with `nextUrl` https://github.com/vercel/next.js/issues/31533#issuecomment-1010597076 – NSjonas Jan 22 '22 at 00:24
-
I guess if you had intranet requests to your service using localhost then it wouldn't work... Not sure how common that actually is with next js (this solution should work for external traffic) – NSjonas Jan 22 '22 at 00:29
-
For anyone searching here, I've found that `req.headers.get('host')` returns the _**true**_ host of the request (e.g. 'yoursite.com'), whereas `req.nextUrl.host` will always return 'localhost:3000' (or whichever port you're using) – caleb531 Jan 24 '22 at 01:24
-
@caleb531 curious, what's your server / network configuration? Where are you hosting? – NSjonas Jan 24 '22 at 15:46
-
3@NSjonas I'm hosting on Heroku — a custom domain with their auto-provisioned SSL. Here's a link to my middleware file for the project: https://github.com/caleb531/faith-dashboard/blob/master/pages/_middleware.ts – caleb531 Jan 24 '22 at 19:13
-
@caleb531 based on the comment, it seems like this is happening when you are running the production build locally? Regardless, if `req.headers.get('host')` is more consistently correct, then I'll updates my answer and package to use that (after testing of course) – NSjonas Jan 24 '22 at 19:51
-
oh nvm, I misunderstood the comment. It looks like you're seeing `nextUrl` as localhost even on heroku production. I wonder if I'm not seeing that because I'm running a different version of nextjs – NSjonas Jan 24 '22 at 19:55
-
so using middleware does NOT give up automatic static optimization, correct? Using a custom server will, is what you're saying. – platypus Oct 10 '22 at 02:34
-
Is it possible to take the query params? I tried everything and it seems to remove them – Or Nakash Oct 30 '22 at 20:53
-
I have a custom server, and I'm hosting in a VPS with an Nginx proxy from a URL (subdomain) to localhost:3000. The solution here does not work because it redirects to https://localhost:3000 and in fact, `req.headers.get('host')` always returns localhost:3000 – noxter Mar 06 '23 at 08:35
-
@noxter one solution might be to control the public host using an ENV (instead of `req.headers.get('host')`), but this would only work if your service is responding to requests from a single domain. – NSjonas Mar 06 '23 at 17:33
-
@NSjonas so by having a subdomain with a proxy this solution is not suitable? – noxter Mar 07 '23 at 13:39
-
@noxter if your proxy is redirecting to `localhost`, then it sounds like you need to find an alternate header to use. You could maybe have the proxy set a custom header, and then use that instead: `req.headers.get('x-redirected-from')`. If you are only serving a single domain, then it seems safer to just store the host in an ENV var – NSjonas Mar 07 '23 at 18:50
-
1Using this solution resulted in an infinite redirect for me. The issue was that `req.headers.get("x-forwarded-proto") !== "https")` always evaluated to true because the header was evaluating to `"https,http"`. Modifying the provided solution to use `.indexOf("https") !== -1` fixed this problem. – Philip Dakin May 25 '23 at 17:57
4
There is a solution with an NPM library called heroku-ssl-redirect
.
First off, install the library using npm i heroku-ssl-redirect
.
Then, create a new server.js
file with the following script.
const next = require('next');
const express = require('express');
const sslRedirect = require('heroku-ssl-redirect').default; // to make it work with 'require' keyword.
const PORT = process.env.PORT || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
// Express's middleware to automatically redirect to 'https'.
server.use(sslRedirect());
server.all('*', (req, res) => {
return handle(req, res);
});
server.listen(port, err => {
if (err) throw err;
console.log(`Server starts on ${PORT}.`);
});
});
Then, change the start
script to be like this:
"scripts": {
"dev": "next",
"build": "next build",
"start": "node server.js"
}
It should work.
Note that you could replace Express with the createServer
method from native Node.js's http
module. But I do this with Express to simplify the syntax.
Further reading: How to setup a custom server in Next.js.

Nicholas
- 2,800
- 1
- 14
- 21
-
So the solution is to set up a custom server. Ok thank you for the answer. Im new to this servers.js but if i weren’t set up a custom server, what kind of server would the webapp be using by default? Just wondering. – jan Mar 03 '21 at 14:56
-
The default one will be using Node's native server. It is one that is made by using Node `http` module. Oh, and please mark the answer as accepted if it helped you. Thanks . – Nicholas Mar 03 '21 at 15:51
-
seems like this should be default out of the box capabilities for next... Surprised the workaround is so heavy... – NSjonas Dec 09 '21 at 21:22
-
If you're using Vercel / netlify / something similar as your deployment platform, then it does perform HTTPS redirection automatically. Especially Vercel, as it's made for Next.js. – Nicholas Dec 10 '21 at 16:27
-