5分で Hello from NextJS

NodeJS のプロジェクトを作りたいので、package.jsonがまだの場合はyarn init -ypackage.jsonを作っておきます。

依存インストール

次にnextをインストールします。nextreactreact-domが共にインストールされていることを期待しているのでその2つもインストールします。

yarn add next react react-dom

設定ファイル作成

🎉next@^9からこのファイルは作成しなくても良くなりました。

2019年10月以前の内容

next.config.jsというファイルを作ります。とはいっても今はただ"Hello from NextJS"と表示させたいだけなので設定内容はただの空オブジェクトです。~~

// next.config.js
module.exports = {};

ページ作成

NextJS ではsrc/pagesディレクトリに実際のページとなるファイルを作成していきます。src/pages/index.jsxindex.htmlというような意味になるのでこのファイルを作成しましょう。

// /pages/index.jsx
export default () => <h1>Hello from NextJS</h1>

ES で書かれていますが、 NextJS は逆に ES でしか書くことができないのでこれで大丈夫です。

ページ確認

./node_modules/.bin/next dev -p 3333と実行しましょう。サーバーが立ち上がり、localhost:3333へアクセスして"Hello from NextJS"と表示されれば大丈夫です。

この結果のリポジトリです。

静的ファイルを配信する

public/ディレクトリへ静的なファイルを置くとそれがそのまま配信されます。例えばpublic/foo.pngのようなファイルを置いたとすると、/foo.pngでアクセスできるようになります。

これは必ずpublic/でなければなりません。src/publicでは配信されないので注意しましょう。

.
├── public
│   └── cats.jpeg
└── src
    └── pages
        └── index.js

またnext@<9(バージョンが9より小さい)頃は、これはstatic/ディレクトリの役割でした。この2つの違いはstatic/では/static/foo.pngのようなファイルにアクセスするには/static/foo.pngとそのまま書かなければなりませんでした。

ルート

pages/以下に置いたファイルは規則によって自動的にルーティング設定されます。例えば、

  • pages/index.js/
  • pages/products/index.js/products
  • pages/products/related.jsは/products/related`

のような感じです。見ての通りファイル構造そのままでルート設定されます。またindex.*な時はディレクトリ名がそのファイルのルートになります。

クエリ付きツート

[ ]で囲んだファイル名・ディレクトリ名(例えばpages/products/[productId].js)にすると、対象のファイルは動的に内容を切り替えられるようになります。その仕組はpages/からの絶対パスの中で[ ]で囲まれた部分はすべてgetInitialPropsqueryプロパティに格納されて来るからです。

例えばpages/products/[productId]/[productColor].jsのようなページの場合、

{
  productId: 123,
  productColor: 'orange'
}

のようなクエリオブジェクトを動的に取得できるので、これを使って結果ページを切り替えることができます。

対象になる pages/ ディレクトリを変更(ソースディレクトリの変更)

🎉nexts@^9.1(2019-11)からデフォルトでpages/の代わりにsrc/pages/ディレクトリでも大丈夫なようになりました。#

よって以下の内容は古くなりました。

nextコマンドの引数の最後はdirです。これはデフォルトでは.が使われて、これで./pagesがページとして処理される形になっています。
なのでsrc/pagesに変更したい場合は、このように実行するだけです。

next src
next build src
next export src

他のコマンドでも同じようにsrc指定が必要です。ちなみにnext.config.jsdirを設定でいいのではと思うかもしれませんが、これを書いてる時点のnext@^8.0.3では何故か効きません。

dir (string) where the Next project is located - default '.'

らしいですが。。

ページ間移動時のアニメーションを実装

react-transition-group_app.jsxの中で使います。_appファイルで定義したコンポーネントはクライアント側で唯一マウントし続けるコンポーネントで、このコンポーネントが受け取るプロパティのComponentに対象のpages/*.jsxファイルのコンポーネントが入る形で来ます。

以下にフェードイン・アウトを想定した例です。( TypeScript ) router.routeCSSTransitionkeyとして使っています。これはページ移動した時にrouter.routeが切り替わり、古いkeyのものはexitに向かい、新しいkeyのページコンポーネントはenterに向かうような形になります。Componentは1つしかないはずですが、TransitionGroupを使うことでアニメーション動作中に限りそれら新古のページコンポーネントを共存させられます。アニメーション中はTransitionGroupが生かしてくれる為です。

export default class extends App {
  addEventListener = (node: HTMLElement, done: () => void) => {
    const handle = () => {
      node.removeEventListener('transitionend', handle);
      done();
    };

    node.addEventListener('transitionend', handle);
  };

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

    return (
      <Container>
        <Provider store={store}>
          <Fade>
            <TransitionGroup>
              <CSSTransition
                key={router.route}
                classNames="fade"
                timeout={1500}
                addEndListener={this.addEventListener}
              >
                <Component {...pageProps} />
              </CSSTransition>
            </TransitionGroup>
          </Fade>
        </Provider>
      </Container>
    );
  }
}

ちなみにFadestyled-componentsになります。これで.fade-<suffix>クラス名を使いアニメーションを実装します。

import styled from 'styled-components';

const Fade = styled.div`
  .fade-enter {}
  .fade-enter-active {}
  .fade-enter-done {}

  .fade-exit {}
  .fade-exit-active {}
  .fade-exit-done {}
`;

Link (next/link) の as 属性は何なのか

NextJS のドキュメントを見ると以下のように使われている例が載っています。このasが何に使われているのかです。

<Link href="/post?slug=something" as="/post/something">

href

hrefに設定する値は NextJS が見る URL です。
NextJS では /pages 以下にページファイルを作っていきます。例えば /foo という URL にページを作りたいなら/pages/foo.jsxというファイルを作り、Linkhref属性には/fooを設定すればいいです。

as

asはブラウザが見る URL です。 例えば、nantoka.com/hoge/以下で NextJS で作ったサイトを公開したい場合、<Link href="/foo" />ではnantoka.com/foo/にページ遷移してしまい、リロードすると NextJS 外の URL なので正常にサイトを表示できなくなってしまいます。
そのような場合にas属性に/hoge/fooと指定してあげることで、ブラウザの URL は nantoka.com/hoge/fooとしながら、表示するページは/foo(/pages/foo)とすることができます。

Styled JSX と TypeScript

Styled JSX を TypeScript と共に使うには<style>タグの属性タイプにjsxglobalを追加する作業から始めなければならないかもしれません。それは以下でできます。

 import * as React from 'react';

 declare module 'react' {
   interface StyleHTMLAttributes<T> extends React.HTMLAttributes<T> {
     jsx?: boolean;
     global?: boolean;
   }
 }

jsx での<style>の属性はReact.StyyleHTMLAttributesによって定義されている為、そこへ TypeScript の同 Interface 名のプロパティ型はマージされる機能を使い、追記しています。

Polyfil ファイルを Webpack の entry に含める

例えばこのサイトだとIntersectionObserverを使っていて、これはスマホなどでまだ対応しておらず見れないことが多いので含めています。

なぜ Webpack の entry に含めたいのか

<script>での読み込みは Lighthouse のスコアを下げる

HTMLで以下の Polyfil を読み込むことで、IntersectionObserverを対応していないサイトでも使えるようにできます。

<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>

しかしこれは、Minimize Critical Requests Depthとして引っかかってしまいます。

NextJS の entry ファイルのルートで Polyfil を読み込む

上のnpm版である、intersection-observerを使います。

Polyfil は基本エントリーの最初に読み込むファイルの先頭にimportを記載すれば使えるので、_app.[jt]sx?をその形にすれば使えそうです。しかし、この Polyfil は中で window オブジェクトへアクセスしている部分があり NextJS ではその_appファイルも SSR で使われるので、 「windowundefinedだよ」なエラーが起こってしまうのでこれも駄目です。

// _app.tsx
import 'intersection-observer';

NextJS の Webpack-Config の entry に含める

next.config.jsからwebpack設定を自身の設定で上書きする方法です。

next.config.jsのデフォルトentry() => Promise<{[x: string]: string[]}>な形をしていて、このオブジェクトを上書きすれば良さそうです。以下は戻り値の例です。

Promise {
  { 'main.js': [],
    'static/runtime/main.js':
     [ '/Users/nju33/github/blog/node_modules/next/dist/client/next-dev' ],
    'static/development/pages/_app.js': [ './pages/_app.tsx' ],
    'static/development/pages/_error.js':
     [ '/Users/nju33/github/blog/node_modules/next/dist/pages/_error.js' ] } }

このmain.jsへ含めてます。

{
  webpack: config => {
    const originalEntry = config.entry;
    config.entry = () => {
      return originalEntry().then(entry => {
        entry['main.js'] = 'intersection-observer';
        return entry;
      }
    }
  }
}

ただまだこれだと、サーバーサイド用のビルドファイルにもintersection-observer Polyfil が含まれてしまい、上記と同じエラーが起きてしまうので、含めるのは Client 用のビルドファイルだけにする必要があります。
幸いにも、next.config.jswebpack関数は第二引数にoptions.isServerというフラグを渡してくれます。これは Client 用のビルド時にはfalseになってくれるフラグです。

つまり、

  • options.isServertrueのときは、デフォルトのままのentryを返す
  • falseのときは、intersection-observerを含める

とすれば解決できそうです。config.entryを以下のように改良してみます。

{
  webpack: (config, {isServer}) => {
    const originalEntry = config.entry;
    config.entry = () => {
      if (isServer) {
        return originalEntry;
      }

      return originalEntry().then(entry => {
        entry['main.js'] = 'intersection-observer';
        return entry;
      }
    }
  }
}

上のように変更したところ Lighthouse のスコアがあがり、かつIntersectionObserverに対応していない端末も対応することができました。

getInitialProps と react-redux の connect で渡す props は後者優先

以下では、getInitialPropsfoo: 1という値で返しているので、this.props.fooには1が入ってきます。が、以下のようにconnectでバインド同じプロパティに対してバインドしてしまうとconnectで渡したmapStateToPropsの値で上書きされてしまいfoo: 9になってしまいます。

class FooBase extends React.Component {
  static getInitialProps() {
    return {
      foo: 1
    }
  }

  render() {
    console.log(this.props.foo)
    // output: 9

    return <div>foo</div>
  }
}

connect(() => ({foo: 9})(FooBase)

少し考えれば当たり前な気もしますが、少しハマりました。ステート構造を見直すなどするといいかもしれません。

サーバーサイドでのみデータを通信して取得後、クライアントサイドでもそれを使いレンダリング

getInitialPropsスタティックメソッドは React のライフサイクルのメソッド群のようにサーバーサイドでもクライアントサイドでも呼び出されます。サーバーサイドで返した値はwindow.__NEXT_DATA__から取得できるので、クライアントサイドではここから値を取得することで通信なしでいける場合があります。

req か res を使ってサーバーサイドでのみ実行される処理を書く

(追記@2019-05-26) 以下のような判定をしなくてもprocess.browserという変数が設定されていました。名前から察せれるようにサーバー側ではfalseでブラウザ側ではtrueになるのでこれを使ったほうが楽です。next@^8.1.0での確認です。

typescript process.browser にアクセスできる型定義

processNodeJS.Processを見ているのでこれを拡張してあげます。

declare namespace NodeJS {
  interface Process {
    browser: boolean;
  }
}

というよりnext@8ぐらいからgetInitialPropsはサーバーでしか動かなくなったかもしれないので以下は不要かもしれません。

このgetInitialPropsに渡ってくるコンテキストのreqresというプロパティはサーバーサイドだけでしか渡ってこない値です。ですのでこれを使ってサーバーサイド判定ができます。

以下は例コードです。reqがある場合はサーバーサイドなのでデータを取得する処理を行い、ない場合(クライアントサイド)ではサーバーサイドで返した値をただ渡すことでクライアントサイドでの通信を無くしています。。

class extends React.Component {
  static async getInitialProps({req}) {
    let data;
    if (req) {
      data = await axios.get('https://xxx.com/xxx')
    } else {
      data = window.__NEXT_DATA__.props.pageProps.data;
    }

    return {data};
  }
}

ターミナルログを非表示にする

やりたいこと

SSR 中のログや、別の所から出したログなどが NextJS ビルとインの(リフレッシュと)ログで消されてしまうので一時的に NextJS のログを表示されないようにしたい。
ちなみにこれのこと。

 [DONE]  Compiled successfully in 2090ms
 [WARNING]  Compiled with 1 warnings

解決

このログは geowarin/friendly-errors-webpack-plugin というプラグインを使って出しているので、このプラグインを取り除きます。
next.config.jswebpack で設定を以下のように変えます。

const path = require('path');

module.exports = {
  webpack(config, {dev}) {
    if (dev) {
      config.plugins = config.plugins.filter(
        plugin => !(plugin.constructor.name === 'FriendlyErrorsWebpackPlugin')
      );
    }

    return config;
  }
};

相対パスな URL に静的ビルドした成果物を置く

例えばhttps://example.com/gameという URL に成果物をアップロードするためにどこか/gameというディレクトリに置く必要があるとします。 その時に、デフォルト設定の成果物をアップロードしてしまうと、 NextJS は/_next/xxx.jsを読もうとしますが実際には/game/_next/xxx.jsである必要がある為読み込むことができません。

そんな時には、next.config.jsassetPrefixを設定します。これで生成されるファイルの参照パスの頭に指定したパスを付けることができます。 また、一応NODE_ENV=productionを付けた時だけプレフィックスを付けるという設定にしています。 これは成果物をローカルで確認したい時にローカルサーバーを建てて見るだけで簡単にチェックできるようにするためです。(/game/_next/xxx.jsとかになるとそういうディレクトリ構造でサーバーを建てないと駄目で面倒)

const isProd = process.env.NODE_ENV === 'production';
module.exports = {
  assetPrefix: isProd ? 'https://example.com/game' : '',
};

package.jsonscriptsは以下のようにします。

{
  "scripts": {
    "next.build": "next build",
    "next.export:prod": "NODE_ENV=production next export",
    "next.export:dev": "next export",
    "build:prod": "yarn next.build && yarn next.export:prod",
    "build:dev": "yarn next.build && yarn next.export:dev
  }
}

あとは適当なbuild.*を実行するといいと思います。

yarn build:prod # 本番用ビルド
yarn build:dev  # 確認用ビルド

Head の chilren として title タグを含む SFC を入れても title 要素にテキストが入らない場合

問題

こんなSFCを用意してました。

export const Meta = ({title}) => {
  return (
    <>
      <meta name="twitter:title" content={title} />
      <meta property="og:title" content={title} />
      <title>{title}</title>
    </>
  );
};

title: 'foo'として展開後こんな感じで入ってくれません。。

<meta name="twitter:title" content="foo" />
<meta property="og:title" content="foo" />
<title></title>

解決

とりあえず直下に持っていけば大丈夫でした。

<Head>
  <Meta title={title} />
  <title>{title}</title>
</Head>

Head コンポーネントの中身は頭に追加される

問題

例えば_document.jsx<head>のデフォルトを定義して各ページでそれを上書きしようとしてもできないものがある。

_document.jsx<head>

<head>
  <title>foo</title>
</head>

pages/index.jsx<Head>

const Head = require('next/head');

<Head>
  <title>bar</title>
<Head>

結果

<title>bar</title>
<title>foo</title>

解決

ページ毎にすべて<Head>に書く。
まぁ<title>は何かうまくやってくれるみたいですが、ツイッターカードとかはうまく取得できてませんでした。

TypeScript で書く

next@8.1.1より@zeit/next-typescriptを使わずにいけるようになりました。Next プロジェクトルートにtsconfig.jsonを起き以下を設定します。

{
  "allowJs": true,
  "skipLibCheck": true,
  "forceConsistentCasingInFileNames": true,
  "noEmit": true,
  "resolveJsonModule": true,
  "isolatedModules": true,
  "jsx": "preserve"
}

これらは Next 側で強制的に上書きされてしまうので上記のまま使うようにします。

あとはそのままページを.tsxで書いていくだけです。

いくつかの記法が使えない

以下の記法を使うとエラーになります。(babelが対応してない?)

  • const enum
  • namespace

注意

declare module '<package-name>' とそれを参照するtypesRoottsconfig.jsonに設定してしまうと、「そんなモジュールない」というエラーが起きてしまうようです。

Web Worker を使う

想定

このような TypeScript な NextJS 環境を想定してます。

依存のインストール

以下の8つをインストールします。

  • next @types/next
  • react @types/react
  • react-dom @types/react-dom
  • @types/node
  • typescript
yarn add {,@types/}{next,react,react-dom} @types/node typescript

トップページ作成

表示が確認できればいいので最低限でsrc/pages/index.tsxファイルを作ります。

mkdir -p src/pages && echo 'export default () => <div>🎉</div>' > src/pages/index.tsx

確認

以下でlocalhost:5489を開き「🎉」と出れば準備完了です。

npx npe scripts.dev 'next --port 5489'
yarn dev

WebWorker 導入

worker-loaderをインストールします。

yarn add -D worker-loader

worker.d.ts を作る

以下の内容で適当な位置(プロジェクト/など)にこのファイルを作ります。

declare module 'worker-loader?name=static/[hash].worker.js!*' {
  class WebpackWorker extends Worker {
    constructor();
  }

  export default WebpackWorker;
}

有効な位置に置けばworker-loader?name=static/[hash].worker.js!../workers/worker.tsとして読み込んだ時に上記の型で読み込めます。

nameオプションでstatic/以下に生成を指定するのは重要で、worker-loader が勝手に設定するパスだと.worker.jsが404で読み込めません。

👍 /_next/static/8f440f04e8526aebb804.worker.js
👎 /_next/8f440f04e8526aebb804.worker.js

Worker ファイルを作る

src/workers/ping-pong.worker.tsを作ります。何かメッセージが来たらpongと返すだけの Worker です。

NextJS により自動生成(または編集)されるtsconfig.jsonによってisolatedModulesが有効になります。このオプションによりモジュールになっていない.tsファイルは怒られます。それを回避する為に、一行目で適当にexportしてます。
このexportはのちにworker-loaderにより上書きされるで適当で大丈夫です。

//! To avoid isolatedModules error 
export default {};

self.addEventListener('message', () => {
  self.postMessage('pong');
});

まだpostMessageなどで型エラーが置きます。これはまだその部分がwindow.postMessageの型だと思ってるからです。

tsconfig.json

Web Worker を使うのでその型を取り込みます。tsconfig.jsoncompilerOptions.libwebworkerを加えます。

{
  "compilerOptions": {
    "lib": [
      "dom",
      "dom.iterable",
      "esnext",
      "webworker"
    ]
  }
}

これで先程のpostMessage周りの型エラーは消えたはずです。

next.config.js

コード分割機能でwindow["webpackChunk"]などにアクセスした時に Web Worker 上はwindowが無い為window is not definedエラーにならないように変更します。それには Webpack の設定を編集し、global.globalObjectselfを指定します。

module.exports = config => {
  config.output.globalObject = 'self';

  return config;
}

使う

あとは使うだけです。src/pages/index.tsxを以下のように書き換えます。

import React, { useEffect } from 'react';

export default () => {
  useEffect(() => {
    (async () => {
      const { default: Worker } = await import('worker-loader?name=static/[hash].worker.js!../workers/ping-pong.worker');

      const worker = new Worker();
      worker.addEventListener('message', event => {
        console.log(event.data);
      })
      worker.postMessage('ping');
    })();
  }, [])

  return <div>🎉</div>
}

Web Worker はブラウザでしか実行できないので、トップレベルでimportするとself周りで(self is not definedのような)エラーになります。なのでuseEffectの中で動的に読み込みます。その時のパスはworker.d.tsに則って記述します。

うまくいくと中身の詰まったdefaultが取り出せます。この中でnew Worker(動的に生成されたjsファイルへのパス)と Worker のインスタンスを作ってくれる関数になってます。

あとは戻り値の Worker で普段のように使うだけです。ping-pong.workerに対して適当なメッセージを送ります。その後ブラウザでpongとコンソールに表示されればうまくできてます!

ここでやったことの結果をリポジトリに置いておきます。

試したこと

@zeit/next-workersも試しましたが効きませんでした。ただ中のindex.jsを見入る感じおかしく無さそうには思いました。

lodash-es を使う

今はnext@8.0.3が最新です。デフォルト設定そのままだと以下のようになってしまいます。

export {default as add} from './add.js'
SyntaxError: Unexpected token export

{#追記 2019-08}

以下の方法はlodash-esを commoonjs に変換して使う方法です。これでは使う意味がないのでやめましょう。代わりにlodashを使いlodash/chunkのような形で取り込む方法が一番*.jsサイズを節約できます。

{/追記}

直す

これにはnext-transpile-modulesを使います。next.config.jsを以下のようにすると直るはずです。

const withTM = require('next-transpile-modules');

module.exports = withTM({
  transpileModules: ['lodash-es'],
});

没案

ちなみに以下のような設定はどちらも駄目でした。。

lodash-es にも next 設定の babel をかます

NextJS の Webpack はnode_modules以下をトランスパイルの対象にしていないようで以下のようにしてみましたが駄目でした。

module.exports = {
  webpack: (config, options) => {
    config.module.rules.push({
      test: /\.js$/,
      include: /node_modules\/lodash-es/,
      use: [
        options.defaultLoaders.babel
      ],
    });

    return config;
  }
}

自分で設定

babel-loader, @babel/core, @babel/plugin-transform-modules-commonjs辺りを入れて自分で設定を追加してみましたがこちらも駄目でした。

module.exports = {
  webpack: config => {
    config.module.rules.push({
      test: /\.js$/,
      include: /node_modules\/lodash-es/,
      use: [
        {
          loader: 'babel-loader',
          options: {
            plugins: ['@babel/plugin-transform-modules-commonjs']
          }
        },
      ],
    });

    return config;
  }
}

commonjs で esnext なパッケージを一緒に使う

対象

cjs で書かれている。つまり以下のように書かれてるもの。例えば、sindresorhus/ky

module.exports = /* ... */

esnext なobject-rest-spread-propertiesなどが使われてる。

// 例: ky/index.js
returnValue = {...returnValue, [key]: value};

問題

  • 対象のファイルを Babel で変換しないとエラー
  • 依存のnext-babel-loaderの中でnextが読み込む.babelrcの設定がmodules:falseなので変換できても cjs が解決できない
  • next.config.js上で Babel の設定を上書きできない
  • .babelrcでは処理的に 1 つの設定しか持てない
  • 自分のソースコードは cjs にしたくない
  webpack(config, {isServer}) {
    if (isServer) {
      return config;
    }

    config.module.rules.map(rule => {
      if (
        rule.test.test('.js') &&
        Object.prototype.hasOwnProperty.call(rule, 'exclude') &&
        rule.exclude.test('node_modules')
      ) {
        rule.exclude = /node_modules\/(?!ky)/;
        // Module build failed: Error: Duplicate plugin/preset detected.
        rule.options = {
          presets: ['next/babel', {
            'preset-env': {
              modules: 'commonjs'
            },
          }]
        }
      }
      return rule;
    });

    return config;
  },

解決

やっぱりこれだけにルール追加するぐらいしかなかった。(読み込んでるプラグインなどはデフォルトの設定と同じもの)

  webpack(config, {isServer}) {
    if (isServer) {
      return config;
    }

    config.module.rules.unshift({
      test: /\.jsx?$/,
      include: /node_modules\/ky/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env'],
          plugins: [
            '@babel/plugin-proposal-class-properties',
            '@babel/plugin-proposal-object-rest-spread',
            '@babel/plugin-transform-runtime',
          ],
        },
      },
    });

    return config;
  },

起こったエラーメモ

デプロイ時にずっと「Initializing」のまま

同プロジェクトでデプロイ中のデプロイメントがあると、それが終わるまで最新のデプロイは「Initializing」になるようです。

now ls <project-name>

を実行し、いらないデプロイメントを削除してみると治るかもしれません。

build 時

UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, open '//../../.svgo.yml'

(node:1) UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, open '//../../.svgo.yml'
    at Object.fs.openSync (fs.js:646:18)
    at Object.fs.readFileSync (fs.js:551:33)
    at e.exports (/var/task/.next/server/static/iqdZhcAsT3LfSmq39Ilkz/pages/_document.js:206:307121)
    at new u (/var/task/.next/server/static/iqdZhcAsT3LfSmq39Ilkz/pages/_document.js:18:123)
    at TPFN.t.default.r.default.plugin (/var/task/.next/server/static/iqdZhcAsT3LfSmq39Ilkz/pages/_document.js:20:223985)
    at n (/var/task/.next/server/static/iqdZhcAsT3LfSmq39Ilkz/pages/_document.js:20:50105)
    at /var/task/.next/server/static/iqdZhcAsT3LfSmq39Ilkz/pages/_document.js:206:21288
    at <anonymous>

now(2)へのデプロイ時に、svgo 付近のパス周りで__dirnameundefinedなのが原因かも。

postcsscssnano_document.tsxで使っていたのでこれをやめて、さらにpostcsscssnanoをアンインストールして依存から外した。これでsvgo依存も無くなり表示できるようになった。

TypeError: r.isMemo is not a function

now(2)へ@now/nextでのデプロイ時に。

https://github.com/zeit/next.js/issues/5750#issuecomment-451607604のとおりに修正で治りました。

Error: pages/index.js from Terser

next.config.jsの Webpack の設定で EnvironmentPluginNODE_ENV=production と上書きしていたので、これをやめた。

TypeError: Cannot read property 'minify' of undefined

2019-02-04発生。

terser@3.14.1を指定する

yarn add terser@3.14.1

package.jsonresolutions

{
  "terser": "3.14.1"
}

export 時

Cannot find module 'pages/_document'

now 用の設定でtarget: 'serverless'していたのでこれを削除で直った。