我正在尝试制作一个简单的 Next.js 应用程序,它使用 Firebase 身份验证并从 Docker 容器运行。
以下在本地工作正常(从构建的 docker 容器运行)。但是,当我部署到 Heroku 或 Google Cloud Run 并访问该网站时,它会导致无限重新加载循环(页面只是冻结并最终耗尽内存。当作为来自 Google 的 Node.js 应用程序提供时,它工作正常应用引擎。
我认为错误出在 Dockerfile 中(我认为我的端口有问题)。Heroku 和 Google Cloud Run 随机化他们的process.env.PORT
环境变量,如果有任何用处,并且EXPOSE
据我所知忽略 Docker 的命令。
重新加载时,网络/控制台中不会显示任何错误。我认为这是由于 Next.js 8 的热module重新加载造成的,但问题在 Next.js 7 上仍然存在。
相关文件如下。
文件
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;
更新:
可重现的 repo 代码在这里。
说明在 README 中,它在本地运行良好。