デプロイ後と同じ環境で開発: now dev

例えば NextJS の開発をnextコマンド(開発サーバーが建つ)で開発してもできますが、now devを使うとデプロイ先と同じソースやデプロイ時に行う動作と同じ流れでビルドしたファイルを使って開発できます。(このコマンドは2020-01時点でまだベータ版な為今後変わる可能性があります)

now devnow.jsonの設定を使うため、このファイルは作っておかなければなりません。とりあえずローカル実行だけなら中身は空の JSON で大丈夫です。

{}

nowではapi/ディレクトリへ.jsファイルを置くと勝手に@now/nodeを使ってサーバーレス関数化されるので、ここではそのファイルを適当api/foo.jsとでも作ります。その中身は以下の通りです。

export default (_, res) => {
  res.send('foo');
}

これでnow devするとapi/fooとアクセスした時にfooと表示されるはずです!

ポート変更

ちなみにnow devはデフォルトで3000で立ち上がりますが--listenフラグを使うことでこれを変えれます。

now dev --listen 3333

環境変数

もしnow.jsonで、now のシークレット変数をenvまたはbuild.envで設定している場合、ローカルにそれぞれ.envenv.buildを作る必要があります。例えばnow.jsonが、

{
  "env": {
    "FOO": "@foo"
  },
  "build": {
    "env": {
      "BAR": "@bar"
    }
  }
}

の場合、.envではFOO=...を、.env.buildではBAR=...を置かなければなりません。

上記の説明の例をココに置いてます。うまくいかない場合に参考にしてください。

デプロイ

準備

コマンドのインストール

now へデプロイするには専門のnowコマンドを使う必要があります。これはyarnnpmでグローバルインストールで入れることができます。

yarn global add now
npm install -g now

now -vでバージョンが出れば完了です。

設定ファイルを作る

now の設定ファイルはnow.jsonという名前で作ります。
now はバーション2になってから様々なデプロイタイプが増えました。例えば静的サイトをただホスティングするだけなら@now/static、 NextJS をデプロイしたいなら@now/next、 Go 言語で書いたものをデプロイしたいなら@now/goなど、自分に環境に合わせて設定ファイルを作ります。

そしてこれらは混合させることができます。例えば、static/ディレクトリ以下は@now/staticgo/以下は@now/goでのようにです。それらはxxx.now.sh/static/index.htmlxxx.now.sh/go/index.goのようなURLで見れる形でデプロイされます。

そのような設定ファイルを作ってみました。ちなみに**/*は再帰的にstatic/以下のすべてのファイルが対象ということになります。(srcへ指定するパスはgulpなどを使っている人などは馴染んでるかもしれません)つまりこれは、static/以下のすべてのファイルがデプロイ対象ということになります。

{
  "builds": [
    {"src": "static/**/*", "use": "@now/static"},
    {"src": "go/**/*", "use": "@now/go"}
  ]
}

デプロイしてみる

これは単にnowと実行するだけです。

now

デプロイが完了するとランダムに生成された URL 上で見れるようになります。ちなみにnow lsで URL などの情報はいつでも見ることができます。

now ls

デプロイ時に--nameオプションを使うことでその名前のグループにデプロイすることができます。例えば、

now --name foo

というような、foo-xxxxxxx.now.shというようなプレフィックスを自分で決めた URL が発行されるようになります。(nameを指定しないと今いるディレクトリ名が使われる)そしてもう一つの利点はnow rm fooとしたときにfooという名前でデプロイしてホスティング中のインスタンスをすべて削除することができます。
now はデプロイしたものは自分で消さないと消えないのでできるだけ名前を付けてデプロイしたほうが楽だと思います。

--name の省略

ちなみに上記はnow.json"name": "foo"と追加すれば省略できるようになります。何度もその名前でデプロイする必要があるなら設定しちゃうのが1番楽だと思います、

これまでのデプロイ情報の確認

now lsまたはnow ls [app]を使います。

すべてのプロジェクトの最新デプロイ履歴を一覧する

単にnow lsを実行します。

now ls
# app    url                     inst #  type  state  age
# nju33  nju33-c00bgh0qj.now.sh       -  -     READY  6h

nowまたはnow deploy時にメタ情報を付けていた場合は同じように-mを使いメタ情報でフィルタリングできます。

あるプロジェクトに関するデプロイ履歴を一覧する

こちらは[app]も指定します。例えばfoo-xxxxxxxx.now.shというような URL の場合fooappです。

now ls foo
# app  url                   inst #   type  state  age
# foo  foo-c00bgh0qj.now.sh       -   -     READY  6h
# foo  foo-5234afnq7.now.sh       -   -     READY  6h

3分で NextJS をデプロイして「 Hello from NextJS 」

必要なファイルは4つです。

  1. NextJS をインストールする為のpackage.json
  2. NextJS の設定ファイルのnext.config.js
  3. now の設定ファイルのnow.json
  4. ページになるpages/index.jsx

package.json

まず NextJS がデプロイ先でインストールされるように、package.jsonを作る必要があります。中身はこんな感じです。

{
  "dependencies": {
    "next": "*"
  }
}

next.config.js

next.config.jsは NextJS の設定ファイルで、 Webpack の設定を弄ったり、静的サイトビルド時のパスといった設定をするファイルなのですが、今回は何もする必要がないので空オブジェクトを指定します。

module.exports = {
  target: 'serverless',
};

now.json

NextJS のデプロイには@now/nextを使います。またその場合srcにはnext.config.jsを指定します。

{
  "builds": [{"src": "next.config.js", "use": "@now/next"}]
}

pages/index.jsx

ページとなるファイルです。これはnext.config.jsが置かれているディレクトリにpages/というディレクトリを作りその中に置く必要があります。中には JSX でHello from NextJSと表示するように書きます。
この辺は now というより NextJS のルールです。

export default () => <div>Hello from NextJS</div>

デプロイ

そして、nowをただ実行するとデプロイが始まりDIR_NAME-xxxxxx.now.shという URL で見れるようになるはずです。そのページを開きこのように表示されれば完了です。

Image from Gyazo

2分で Go をデプロイして「 Hello from Go 」

設定ファイルを作る

こんな感じのnow.jsonファイルを作ります。これは作業ディレクトリ内にあるすべての*.goファイルを@now/goを使ってデプロイするという意味になります。

{
  "builds": [{"src": "**/*.go", "use": "@now/go"}]
}

Go ファイルを作る

以下のようなファイルを作ります。現段階では、各*.goファイルではHandlerという関数を必ず定義する必要があります。(now 側がこの関数を呼び出します)

package main

import (
    "fmt"
    "net/http"
)

func Handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "<h1>Hello from Go</h1>")
}

デプロイ

あとは以下でデプロイします。

now

DIR_NAME-xxxxxxx.now.shのような URL で見れたら完了です。

Image from Gyazo

now.json の為の JSON Schema

作りました。

スキーマはunpkg.com/@nju33/json-schema/schemas/now/now.jsonに置いてあります。(設定時は先に先の URL を使う必要があるので注意です)

VSCode の設定

コマンドパレットへ>Preferences: Open Settings (JSON)と入力し設定ファイルを開きます。そして中でjson.schemasへ以下のような設定をします。

{
  "json.schemas": [
    {
      "fileMatch": ["now.json"],
      "url": "https://unpkg.com/@nju33/json-schema@0.0.19/schemas/now/now.json"
    },
  ]
}

now.json を編集

適当にプロジェクトルートにnow.jsonファイルを作り、内容が{}の状態で「Missing property version」のように警告されれば完了です。

ワンライナーでデプロイ・エイリアス作成・古くなった環境の削除

なるべく短くするためにnow.jsonにはnamealiasを設定します。以下この設定での話です。

{
  "version": 2,
  "name": "foo",
  "alias": "foo",
  "builds": [...]
}

コマンド

そして以下を実行します。

2019年4月更新
now && now alias && now rm -sy <name>

上記は代わりました。2019年4月以降aliasは使わなくて良くなり、now--target productionオプションを付けることでnow.jsonaliasセクションがある場合、それを自動で設定するようになりました。

よって、上記コマンドは以下のように変更になりました。

now --target production && now rm -sy <name>

now

これはデプロイコマンドですね。これが完了するとfoo-xxxxxxx.now.shという URL が発行されます。

now alias

これによりfoo-xxxxxx.now.shという URL が(空いていれば)foo.now.shに紐づけ設定されます。

now rm -sy foo

now rm foofooという名前でデプロイしたものすべての環境を削除しようとします。この時yes/noで「本当に削除しますか?」と聞いてくるので-y(--yes) を付けることでこれに先に答えています。また、-s(--safe)を付けると削除対象からエイリアスと紐付いているものを外すことができます。つまり、

foo-xxxxxx.now.sh -> foo.now.sh
foo-yyyyyy.now.sh
foo-zzzzzz.now.sh

のときに、now rm -sy fooした場合foo-xxxxxx.now.shだけが残りそのエイリアスも残ってくれるはずです。

環境変数を設定する

@now/nextではbuild.env、それ以外ではenvマシンの環境変数を渡すことができます。

{
  "env": {
    "FOO": "BAR"
  },
  "build": {
    "env": {
      "FOO": "BAR"
    }
  }
}

隠したい値にはシークレット変数を設定する

「何とかkey」や「何とかtoken」のようにあまり見られたくない値があると思います。その場合はまずシークレット変数として、 now に登録してそれを使うようにします。これの登録にはnow secretコマンドを使います。

以下はtokenxxxxxxという値を登録する例です。

now secret add token xxxxxx

登録が終わったら先程のenv設定で頭に@を付けて指定してあげるだけです。

{
  "env": {
    "TOKEN": "@token"
  },
  "build": {
    "env": {
      "TOKEN": "@token"
    }
  }
}

環境変数が登録されたか確認する方法

そういうログを吐いて、それをnow logsコマンドから見てチェックします。now logs <url> -n 500(500行)とすれば確実に見れると思います。

@now/node

これは中でconsole.log(process.env)とすればログを吐いてくれるので、適当にアクセスしたあとにnow logsでその部分をチェックします。

@now/next

@now/nextを使ったデプロイでは、デプロイフローの中でyarn now-buildが呼ばれています。なので、これにフックする形で吐かせるといいと思います。(頭にpreを付ける)

printenvはマシンに設定されている環境変数をすべて表示するコマンドです。

{
  "scripts": {
    "prenow-build": "printenv"
  }
}

NextJS の中で環境変数が見れるようにする

まだマシンに環境変数を設定しただけで NextJS の中では見ることができません。これにはnext.config.jsの Webpack の設定でwebpack.EnvironmentPluginなどを使って定義します。

先に Webpack をインストールだけしておきます。

yarn add -D webpack

そして、こんな感じでnext.config.jsの Webpack 設定に手を入れます。

module.exports = {
  webpack: config => {
    /* ...色々 */
    
    const webpack = require('webpack');
    config.plugins.push(
      new webpack.EnvironmentPlugin({
        TOKEN: 'default value'
      })
    );
  }
}

これでコードの中のprocess.env.TOKENで値が取得できるようになるはずです。

シークレット情報

たとえ同じチームの人であっても生データを見られたく情報はnow secretコマンドで登録することで安全にnow.jsonの環境変数envなどに書けます。

now secret

このコマンドができるのは、

  1. add
  2. rename
  3. rm
  4. ls

の4つです。

add

シークレット情報を追加します。例えば、以下はfooという名前でcbTRc45FDを登録しています。

now secret add foo cbTRc45FD

rename

シークレット情報の名前を変更します。先程のfoobarに変えるなら以下のようにします。

now secret rename foo bar

ls

シークレット情報を一覧します。どれくらい前に登録したかも表示されます。

now secret ls
# name         created
# xxx          521d ago
# bar          2m ago

先程のbarが登録されていることが確認できました。

rm

シークレット情報を削除します。一連の確認が済んだbarはもう不要なので以下で削除します。
途中確認を聞かれるのでyと入力します。

now secret rm bar
# now> The following secret will be removed permanently
#   bar      3m ago
# > Are you sure? [y/y    

再度lsで無くなっていれば完了です!

now secret ls
# name         created
# xxx          521d ago

now.json で使う

envenv.buildなどにはシークレット情報が使えます。頭に@を置いて@<secret-name>のような形値を環境変数の値として置きます。

{
  "env": {
    "FOO": "@foo"
  },
  "env": {
    "build": {
      "BAR": "@bar"
    }
  }
}

now dev を使ったローカル開発

envenv.buildにシークレット情報を使っているプロジェクトで、now devすると.env.env.buildが要求されるようになります。

前セクションのような設定であればそれぞれ以下のような.envっと.env.buildを作ります。

FOO=開発用の値
BAR=開発用の値

now でドメインを買ったらやること

そのドメインを自分のアカウント・チームに追加

ドメインは自分のアカウントに買ったという情報だけある状態なので、まず使いたいアカウント・チームに追加してあげる必要があります。

now switch <追加したいアカウント・チーム>
now domains add <domain>

ちなみに間違えた場合はnow domains rm <domain>です。

TXT レコードを追加

now domains inspect <domain>を実行すると、

  Verification Record

    name        type        value
    _now        TXT         xxxxxxxxxxxxxxxx

のような部分が表示されると思います。上記の場合以下のようなコマンドを実行します。

now dns add <domain> _now TXT xxxxxxxxxxxxxxxx

ネームサーバーと TXT 認証が終わるまで暫く待つ

これには僕は1時間ぐらい掛かったと思うので、焦らず待ちます。再度、now domains inspect <domain>を実行した時に、

    nsVerifiedAt        -
    txtVerifiedAt       -

ここが-じゃなく日付に変わっていればnow aliasなどで使えるようになっているはずです。

買ったドメインでサブドメイン設定

now.jsonaliasに設定したいサブドメインを指定します。

{
  "alias": "foo.<my-domain>"
}

あとは以下を実行すると勝手に設定してくれます。

now alias

手作業で設定する

`nowでビルドしたあとに、foo-xxxxxx.now.sh`のような URL が発行されますが、これを使って以下のように紐付けることもできます。

now alias set foo-xxxxxx.now.sh foo.<my-domain>

こちらは毎回ビルドのあと設定する必要があります。

買ったドメインで AWS SES の設定をする

now でドメインは購入済みだとします。

AWS SES でドメインを追加する

SESのホームへ行き「Domains」 → 「Verify New Domain」から now で購入したドメインを入力します。

Image from Gyazo

すると以下のようなname type valueを取得できます。

Image from Gyazo

now コマンドで DNS 設定を更新する

このようなコマンドを実行するだけです。

now dns add <自身のドメイン> __amazonses TXT <上記のvalue>

上記のコマンドがちゃんと設定できたか確認をします。

now dns ls <自身のドメイン>

ちゃんと設定できたようなら僕の場合は30分ぐらいで以下のメールが来て認証されました。

Image from Gyazo

__amazonses.<自身のドメイン> じゃないのか?

SES で「設定してください」と言われる名前は__amazonses.<自身のドメイン>なので僕も最初はこれを設定して試しました。ですが、数日経っても認証されなかったので now にフィードバックを送ってみた所「__amazonsesでやってみて」と返信があったのでした。

Image from Gyazo

ログを確認する

コマンド

now logsを使います。例えばこのサイトは now で運用していますが、このサイトのログを見たいなら以下のコマンドを実行します。

now logs nju33.com

オプション

-nを付けると何行分表示するかを設定できます。デフォルトでは100です。例えば1000行表示したいなら、

now logs nju33.com -n 1000

とします。

-fを付けると更新を待機して、更新があった時に自動で表示します。less -fみたいな感じです。

now logs nju33.com -f # または --follow

ブラウザ

now にログインしていればブラウザ上でログを見ることもできます。now ではnow.jsonroutesを何も設定していなくても、/_logsという特殊なルーティングを設定します。この URL にアクセスすると今ターミナルで見ていたログと同じものを見ることができます。例えばこのサイトであれば nju33.com/_logsという URL になります。

ちゃんとファイルがデプロイされているか確認する

now でデプロイすると自動でユニークな URL が発行されます。この時/_srcというルーティングも自動で設定されこの URL へアクセスすることでどういったファイルがアップロードされたのか、ファインダーのような形で確認することができます。

このサイトも now でホスティングしていますが、このサイトの場合nju33.com/_srcという URL になります。

CircleCI 上でデプロイする

デプロイする前にnow loginが必要ですが、これはメール認証なので CI 上で自動化できません。自動化したい場所では認証トークンを使います。

トークンの発行

zeit.co/account/tokensから発行できます。個人アカウントから発行します。チームアカウント上で使いたい場合も、その個人アカウントが使いたいチームに所属していれば使うことができるので大丈夫です。

Image from Gyazo

チームとトークンを指定してデプロイ

コマンド毎に--team--tokenで渡して実行します。(チーム(--team,-T)を省略するの個人アカウントがデフォルトで設定されます)--tokenは環境によって環境変数に置いたりして渡しましょう。以下はfooチーム上でデプロイする例です。自分がこのチームに所属していれば正常にデプロイできるはずです。

now --prod --team foo --token xxxxx && \
  now rm foo.now.sh --team foo --token xxxxx -sy
2019年頃の方法
now --team foo --token xxxxx && \
  now alias --team foo --token xxxxx && \
  now rm foo.now.sh --team foo --token xxxxx

npm のプライベートパッケージを使いたい時

そのままではプライベートパッケージを見ることができないので、nowのデプロイフローがエラーになってしまします。

この問題を解決するにはプロジェクトのルートディレクトリに.npmrcを追加しデプロイファイルに含まれるようにしてデプロイします。.npmrcはこんな感じの内容で作ります。

//registry.npmjs.org/:_authToken=...

authTokenauthTokenを取得の方法で取得してください。

領収書を晒す

now2になってから料金体制がよく分からないので少しでも参考になれば。

2019年05月20日から2019年06月20日

運用サイトは10程度(ただしアクセスはあまりない(/゜Д゜)/┻┻)です。

Image from Gyazo

GitHub連携によって自動でステージング反映とかしているので、気になるならそれを切ればもっと安くなりそうですね。

ログは少し節約してたんですが、もっとガンガン吐いても良さそう。

起こったエラーメモ

デプロイ時にずっと「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'していたのでこれを削除で直った。

Error: Cannot export when target is not server. https://err.sh/zeit/next.js/next-export-serverless

next.config.jsでエクスポートしているオブジェクトにtarget: 'serverless'となっている可能性があります。静的ビルドの場合ここはtarget: 'server'(デフォルト値)が正解です。

値を変更後はnext buildnext exportと再度ビルドから行います。