2020-04-24に更新

Vercel(元ZeitのNow)にてNext.jsでnode-canvasを使ってOGP

Next.jsアプリケーションをVercel(元ZeitのNow)で利用してOGPを生成しようと思ったが無理で、それでもなんとか成功させたある男の記録(2020/4、Next.js9.3現在)。

VercelでNext.js

そもそもNext.jsはVercelの人が作っている。そのため多分かなり親和性が高い。GitHub連携して適当にpushすればすぐなんの設定もなくすぐデプロイできる。ということでまずは何も考えずガリガリアプリケーションを作成していた。

何がうまくいかなかったのか

まず作っていたもの。それは普通のNext.jsアプリケーション。それにOGPもつけようと思っていた。調べてみるとNext.jsには API Routes という機能があり、別途サーバー用の機能を準備しなくてもちょっとしたAPIならpages/apiの下に通常のページと同じ配置でメソッドを追加すればサーバーサイドの処理も書けてしまう。

これでOPGも可能そうだと思い、事前にnode-canvasを使って試していたところうまくいっていた。前も別のところで同じことをやっていたのだが、基本的にこういうデプロイ先はフォントが無いことがあるため、node-canvasにあるregisterFontという機能を使って直接ttfのようなフォントファイルを読み込むことで文字も描画できるようにしている。

しかし、これがうまくいかなかった。なんかregisterFontあたりエラーが出る。調べてみたところこんなissueをみつけた。

Next.js API routes (and pages) should support reading files · Issue #8251 · zeit/next.js

そう、なんか __dirname もちゃんと機能していないし、fs.readFileSync もまともに動かない……。

ということで、フォントの読み込みができなかった。

なぜファイルを読み込めないのか

なぜかはよくわからないが、とにかくVercelは内部的にはAWSのLambdaを使っており、その関係でデプロイする時に関係ないファイルを無視していたり、フォルダ構成も全く違う感じになってたり、色んな要因でうまくいかないのではないかと思われる。詳しくはわからない。が、上記issueやこのPR Fix for __dirname by huv1k · Pull Request #8334 · zeit/next.js を見る限り、なかなか改善できていないようなので多分構成的にそんな簡単ではないのではないかと思われる。

ということで、そのうち改善されるのかもしれないが、とにかく今や今後すぐに改善されるとは思わないほうが良いっぽく、無理なら無理でどうするかを考える必要がありそうだった。

解決の手がかり

実はこのissueの中に、下記のようなコメントがあった。結果としてはこれが解決方法。

https://github.com/zeit/next.js/issues/8251#issuecomment-614220305

具体的には @now/next@now/node のbuildersを併用するというもの。

buildersとは

Vercelにはbuildersという概念があり、これによりどの様にアプリケーションをデプロイするかを決めることができる。 @now/next はNext.jsをデプロイする時に自動的に選択される、Next.js用のbuilders。 @now/nodeはそれ以外のNode.jsアプリケーション。

なぜ併用するのか

ではなぜ併用する必要があるのかと言うと、先程のissueにも書かれているのだが、実はincludeFilesという設定があり、これを利用すると指定したファイルをデプロイ時に無視しないようにし、正常にデプロイ先で読み込むことができるようになる。

ところが、これが現在 @now/next では利用できない。そのため、 @now/node を併用して利用することでOPGの生成のようにファイルを読み込む日強グアある処理だけはそちらで稼働させようという考え。

実際に解決した方法

まず、pages/apiにあった処理を functions/index.ts に移した。Next.js側では使えないため、API Routesも使えない。そのため Next.jsの外に出して別で稼働させる。apiというフォルダに入れれば @now/node で /api/~~ というURLで実行できるのだが、これはNext.jsのAPI Routesと被ってしまうので今回はとりあえず /functions/~~ というURLでアクセスできるようにした。具体的なnow.jsonの設定は下記の通り(最近はGitHub経由でデプロイできるし、独自ドメインや環境変数も管理画面で設定できるのでそもそもnow.json自体なく、今回追加したこれで全部)。

{
  "builds": [
    {
      "src": "next.config.js",
      "use": "@now/next"
    },
    {
      "src": "functions/index.ts",
      "use": "@now/node",
      "config": {
        "includeFiles": ["fonts/*"]
      }
    }
  ],
  "routes": [
    {
      "src": "/functions/(.+)",
      "dest": "/functions/index.ts"
    }
  ]
}

これでfontsというフォントを入れたフォルダを維持したまま、functionsで始まるURLで @now/node ビルドのサーバーサイド処理にアクセスすることができる。

で、色々端折るけどfunctions/index.tsはこんな感じ。

import * as path from 'path'
const { createCanvas, registerFont } = require('canvas')
import { NowRequest, NowResponse } from '@now/node'

export default function (req: NowRequest, res: NowResponse) {
  registerFont(path.join('fonts', 'myfont.ttf'), {
    family: 'myfont',
  })

  const canvas = createCanvas(600, 315)
  const context = canvas.getContext('2d')

  context.font = '15px myfont'
  context.fillStyle = '#424242'
  context.fillText('hello', 100, 100)

  const image = canvas.toBuffer()

  res.writeHead(200, {
    'Content-Type': 'image/png',
    'Content-Length': image.length,
  })
  res.end(image, 'binary')
}

ちなみに、あくまでも @now/next と @now/node は別環境のため、フォントをアップできたからといってNext.js側でそれを利用することは出来ない。今回はOGPだったためURLだけで連携できるのでOKだった。

まとめ

このように、一時期は別サーバーで対応が必要かとも考えたが、なんとかVercelだけで全てを完結させることができたので本当に良かった。Vercel最高。だけどはやくファイルの読み込み関連はいい感じに使えるように改善して欲しいものだ。

ちなみに実際に作ったのはこれ(石を進めたときだけOGPが出る)

リバーシ棋譜ツール

ツイッターでシェア
みんなに共有、忘れないようにメモ

だら@Crieit開発者

Crieitの開発者です。 Webエンジニアです(在宅)。大体10年ちょい。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel, Vue.js, React, Node, RoR 趣味:Elixir, Phoenix, Nuxt, Express, GCP, AWS等色々

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!
ボードとは?

関連記事

コメント