How do I force or redirect my next.js website to use https
Asked Answered
F

3

7

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.

Fantasize answered 3/3, 2021 at 13:28 Comment(1)
You can use custom next.js server or nginx: nextjs.org/docs/advanced-features/custom-serverAspia
F
27

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({});
Forebear answered 10/12, 2021 at 23:53 Comment(16)
Problem with this is, where ever you deploy this code, req.nextUrl.hostname will always resolve to the localhost. Because server internally runs on localhost. example.com/page-1 ===> localhost/page-1Cullan
@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 github.com/vercel/next.js/issues/31533#issuecomment-1010597076Forebear
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)Forebear
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)Dyche
@Dyche curious, what's your server / network configuration? Where are you hosting?Forebear
@Forebear I'm hosting on Heroku — a custom domain with their auto-provisioned SSL. Here's a link to my middleware file for the project: github.com/caleb531/faith-dashboard/blob/master/pages/…Dyche
@Dyche 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)Forebear
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 nextjsForebear
so using middleware does NOT give up automatic static optimization, correct? Using a custom server will, is what you're saying.Einkorn
Is it possible to take the query params? I tried everything and it seems to remove themErmentrude
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 localhost:3000 and in fact, req.headers.get('host') always returns localhost:3000Downspout
@Downspout 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.Forebear
@Forebear so by having a subdomain with a proxy this solution is not suitable?Downspout
@Downspout 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 varForebear
Using 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.Lima
just a small addition to preserve query params return NextResponse.redirect(https://${req.headers.get('host')}${req.nextUrl.pathname}${req.nextUrl.search}, 301);Centurion
S
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.

Splenius answered 3/3, 2021 at 13:51 Comment(5)
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.Fantasize
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 😀.Splenius
seems like this should be default out of the box capabilities for next... Surprised the workaround is so heavy...Forebear
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.Splenius
Note: heroku does not automatically redirect to httpsForebear
P
0

in ur package.json > scripts > "dev":"next dev -experimental-https"

i hope that will help u :)

Protector answered 6/7 at 16:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.