Next.js 从 Docker 容器无限重载

2024-04-13

我正在尝试制作一个简单的 Next.js 应用程序,它使用 Firebase 身份验证并从 Docker 容器运行。

以下内容在本地运行良好(从构建的 docker 容器运行)。然而,当我部署到 Heroku 或 Google Cloud Run 并访问该网站时,它会导致无限的重新加载循环(页面冻结并最终耗尽内存。当作为 Google 的 Node.js 应用程序提供服务时,它工作正常应用程序引擎。

我认为错误出现在 Dockerfile 中(我认为我对端口做了一些错误的操作)。 Heroku 和 Google Cloud Run 随机化他们的process.env.PORT环境变量,如果有的话,忽略 Docker 的EXPOSE据我所知的命令。

重新加载时,网络/控制台中不会显示任何错误。我认为这是由于 Next.js 8 的热模块重新加载造成的,但问题在 Next.js 7 上也仍然存在。

相关文件如下。

Dockerfile

FROM node:10

WORKDIR /usr/src/app

COPY package*.json ./
RUN yarn

# Copy source files.
COPY . .

# Build app.
RUN yarn build

# Run app.
CMD [ "yarn", "start" ]

服务器.js

require(`dotenv`).config();

const express = require(`express`);
const bodyParser = require(`body-parser`);
const session = require(`express-session`);
const FileStore = require(`session-file-store`)(session);
const next = require(`next`);
const admin = require(`firebase-admin`);
const { serverCreds } = require(`./firebaseCreds`);

const COOKIE_MAX_AGE = 604800000; // One week.

const port = process.env.PORT;
const dev = process.env.NODE_ENV !== `production`;
const secret = process.env.SECRET;

const app = next({ dev });
const handle = app.getRequestHandler();

const firebase = admin.initializeApp(
  {
    credential: admin.credential.cert(serverCreds),
    databaseURL: process.env.FIREBASE_DATABASE_URL,
  },
  `server`,
);

app.prepare().then(() => {
  const server = express();

  server.use(bodyParser.json());
  server.use(
    session({
      secret,
      saveUninitialized: true,
      store: new FileStore({ path: `/tmp/sessions`, secret }),
      resave: false,
      rolling: true,
      httpOnly: true,
      cookie: { maxAge: COOKIE_MAX_AGE },
    }),
  );

  server.use((req, res, next) => {
    req.firebaseServer = firebase;
    next();
  });

  server.post(`/api/login`, (req, res) => {
    if (!req.body) return res.sendStatus(400);

    const { token } = req.body;
    firebase
      .auth()
      .verifyIdToken(token)
      .then((decodedToken) => {
        req.session.decodedToken = decodedToken;
        return decodedToken;
      })
      .then(decodedToken => res.json({ status: true, decodedToken }))
      .catch(error => res.json({ error }));
  });

  server.post(`/api/logout`, (req, res) => {
    req.session.decodedToken = null;
    res.json({ status: true });
  });

  server.get(`/profile`, (req, res) => {
    const actualPage = `/profile`;
    const queryParams = { surname: req.query.surname };
    app.render(req, res, actualPage, queryParams);
  });

  server.get(`*`, (req, res) => handle(req, res));

  server.listen(port, (err) => {
    if (err) throw err;
    console.log(`Server running on port: ${port}`);
  });
});

_app.js

import React from "react";
import App, { Container } from "next/app";
import firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";
import "isomorphic-unfetch";
import { clientCreds } from "../firebaseCreds";
import { UserContext } from "../context/user";
import { login, logout } from "../api/auth";

const login = ({ user }) => user.getIdToken().then(token => fetch(`/api/login`, {
  method: `POST`,
  headers: new Headers({ "Content-Type": `application/json` }),
  credentials: `same-origin`,
  body: JSON.stringify({ token }),
}));

const logout = () => fetch(`/api/logout`, {
  method: `POST`,
  credentials: `same-origin`,
});

class MyApp extends App {
  static async getInitialProps({ ctx, Component }) {
    // Get Firebase User from the request if it exists.
    const user = getUserFromCtx({ ctx });
    const pageProps = Component.getInitialProps ? await Component.getInitialProps({ ctx }) : {};
    return { user, pageProps };
  }

  constructor(props) {
    super(props);
    const { user } = props;
    this.state = {
      user,
    };

    if (firebase.apps.length === 0) {
      firebase.initializeApp(clientCreds);
    }
  }

  componentDidMount() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        login({ user });
        return this.setState({ user });
      }
    });
  }

  doLogin = () => {
    firebase.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider());
  };

  doLogout = () => {
    firebase
      .auth()
      .signOut()
      .then(() => {
        logout();
        return this.setState({ user: null });
      });
  };

  render() {
    const { Component, pageProps } = this.props;

    return (
      <Container>
        <UserContext.Provider
          value={{
            user: this.state.user,
            login: this.doLogin,
            logout: this.doLogout,
            userLoading: this.userLoading,
          }}
        >
          <Component {...pageProps} />
        </UserContext.Provider>
      </Container>
    );
  }
}

export default MyApp;

Update:

可重现的回购代码是here https://github.com/colinricardo/next-issue.

说明在自述文件中,并且在本地运行良好。


对服务器环境变量进行硬编码(而不是从 Heroku / Cloud Run 读取它们)可以解决此问题。

这样做的原因似乎是因为 Heroku / Cloud Run 上的环境变量在运行时可用,但在构建时不可用,因此 Docker 环境(和server.js) 无法从以下位置访问它们process.env。 Google App Engine 也存在类似问题here https://stackoverflow.com/questions/55892795/google-app-engine-modify-cloud-run-environment/55915774#55915774.

该解决方案并不理想,因为您可能必须保留config/staging.js在版本控制中,它会导致不同分支之间的合并冲突,但这种冲突应该只发生一次。

服务器.js

const { envType } = require(`./utils/envType`);
const envPath = `./config/${envType}.js`; // e.g. config/staging.js with env variables
const { env } = require(envPath);
...
const { envType } = require(`./utils/envType`);
const envPath = `./config/${envType}.js`;
const { env } = require(envPath);

const nextConfig = {
  env: { ...env },
};

module.exports = nextConfig;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Next.js 从 Docker 容器无限重载 的相关文章

随机推荐