Next.js 从 Docker 容器无限重新加载
Posted
技术标签:
【中文标题】Next.js 从 Docker 容器无限重新加载【英文标题】:Next.js infinite reload from Docker container 【发布时间】:2019-09-17 00:11:46 【问题描述】:我正在尝试制作一个简单的 Next.js 应用,它使用 Firebase 身份验证并从 Docker 容器运行。
以下在本地运行良好(从构建的 docker 容器运行)。但是,当我部署到 Heroku 或 Google Cloud Run 并访问网站时,它会导致无限的重新加载循环(页面只是冻结并最终耗尽内存。当作为 Node.js 应用程序从 Google 提供时它工作正常应用引擎。
我认为错误在 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" ]
server.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;
更新:
可重现的回购代码是here。
说明在自述文件中,并且在本地可以正常工作。
【问题讨论】:
你试过本地NODE_ENV制作吗? 是的,工作正常! 已经解决了吗? @Towkir 不,仍然是个问题。 你用来在本地测试它的“docker run”命令是什么?根据您的 Dockerfile,即使在本地也不应该工作,因为容器内部没有 EXPOSED 端口。还有你用来测试它的 URL 是什么? 【参考方案1】:硬编码服务器环境变量(而不是从 Heroku / Cloud Run 读取它们)解决了这个问题。
原因似乎是因为 Heroku / Cloud Run 上的环境变量在运行时可用,但在构建时不可用,因此 Docker 环境(和 server.js
)无法从 process.env
访问它们。 Google App Engine here 也存在类似问题。
此解决方案并不理想,因为您可能必须将config/staging.js
保留在版本控制中,它会导致不同分支之间的合并冲突,但该冲突应该只发生一次。
server.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;
【讨论】:
以上是关于Next.js 从 Docker 容器无限重新加载的主要内容,如果未能解决你的问题,请参考以下文章
TypeError: Object(...) is not a function 当将卷从本地目录挂载到 next.js 容器中时