Next.js бесконечная перезагрузка из контейнера Docker - PullRequest
4 голосов
/ 28 апреля 2019

Я пытаюсь создать простое приложение Next.js, которое использует аутентификацию Firebase и запускается из контейнера Docker.

Следующее прекрасно работает локально (работает из встроенного контейнера Docker).Однако когда я выполняю развертывание в Heroku или Google Cloud Run и захожу на веб-сайт, это приводит к бесконечному циклу перезагрузки (страница просто зависает и в конечном итоге не хватает памяти. Она отлично работает, когда ее обслуживают как приложение Node.js от Google.App Engine.

Я думаю, что ошибка в Dockerfile (я думаю, что я делаю что-то не так с портами). Heroku и Google Cloud Run рандомизируют свою переменную окружения process.env.PORT, если это используется, иНасколько мне известно, игнорируйте команды EXPOSE Докера.

В Network / Console при перезагрузке не отображается никаких ошибок. Я думал, что это произошло из-за перезагрузки горячего модуля 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;

Обновление:

Воспроизводимый код репо: здесь .

Инструкции в README, и они прекрасно работают локально.

1 Ответ

1 голос
/ 06 мая 2019

Жесткое кодирование переменных среды сервера (вместо чтения их из Heroku / Cloud Run) решает эту проблему.

Причина этого, по-видимому, заключается в том, что переменные среды в Heroku / Cloud Run доступны во время выполнения, но не во время сборки, поэтому среда Docker (и server.js) не имеет к ним доступа из process.env,Подобная проблема с Google App Engine здесь .

Это решение не идеально, поскольку вам, возможно, придется держать 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...