Open11

モノレポに配置しているnext.jsアプリケーションとserverless functionを、1つのVercel Projectに同居して動かしたい

Hirotaka MiyagiHirotaka Miyagi

github-nippou-webというツールを作っている。その名の通り「日報」を書くためのツール。GitHubアカウントでログインし、指定した期間で自分が関与したissue, PRをMarkdownのリスト形式で列挙してくれる。

https://github-nippou-web.vercel.app/

https://github.com/MH4GF/github-nippou-web

このリポジトリでは/webにnext.jsのアプリを、/api にGoのハンドラを置き、どちらもVercelで動かしている。
GoのハンドラをVercelで動かす、というのはVercelのServerless Functionsの機能を使っている

https://vercel.com/docs/functions/serverless-functions

1つのリポジトリだが、next.jsアプリとGoのハンドラをVercelの別々のプロジェクトで動かしている。

Hirotaka MiyagiHirotaka Miyagi

達成したいことは、別々のプロジェクトで動かしているNext.jsアプリとGoのハンドラを一つのプロジェクトに統合できないか?というもの。

  • 1リポジトリ : 1プロジェクトでシンプルになる
  • ドメインが同じになるので、APIを呼び出す際も相対パスで呼び出せる
    • 現在はできないため、プレビュー環境でAPIを呼び出すことができていない
  • ローカル開発 vercel dev がうまく動作しておらず、APIのデバッグがしづらい
Hirotaka MiyagiHirotaka Miyagi

前提となるGoのハンドラは以下

https://github.com/MH4GF/github-nippou-web/blob/5399a1608af4c9b8dde9314b5346223d0bf725ef/api/api/index.go

61行の単純なコードとなっている。これをVercelにデプロイし、 ドメイン/api/index にアクセスすると関数を動作させられる。

Vercelの規約としては以下を満たす必要がある

  • /api 以下に関数ファイルを置く
  • Goの場合、 http.HandlerFunc を満たす実装とする

https://vercel.com/docs/functions/serverless-functions/runtimes/go

また、モノレポとしてファイルを配置しているため Root directoryを /api にしている

Hirotaka MiyagiHirotaka Miyagi

vercel dev でGoのハンドラが識別されるかを見てみる

vercel dev
Vercel CLI 32.5.0
? Set up and develop “~/ghq/github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs”? [Y/n] y
? Which scope should contain your project? xxx
? Link to existing project? [y/N] n
? What’s your project’s name? debug-vercle-serverless-function-golang-with-nextjs
? In which directory is your code located? ./
Local settings detected in vercel.json:
Auto-detected Project Settings (Next.js):
- Build Command: next build
- Development Command: next dev --port $PORT
- Install Command: `yarn install`, `pnpm install`, `npm install`, or `bun install`
- Output Directory: Next.js default
? Want to modify these settings? [y/N] n
🔗  Linked to mh4gf/debug-vercle-serverless-function-golang-with-nextjs (created .vercel)
> Running Dev Command “next dev --port $PORT”
   ▲ Next.js 14.0.3
   - Local:        http://localhost:3000

> Ready! Available at http://localhost:3000
 ✓ Ready in 2.5s
 ○ Compiling / ...
 ✓ Compiled / in 2.2s (501 modules)
 ✓ Compiled in 108ms (235 modules)

プロンプトに従って進めてみたが、Auto-detectedされたのはnext.jsアプリのように見える。
Goのハンドラは /api/index.go に置いたので、http://localhost:3000/api/index にアクセスしてみる

お!?

404になるかと思っていたが504、ハンドラとして登録はされているのか?
ログは以下のように出ている

> Ready! Available at http://localhost:3000
 ✓ Ready in 2.5s
 ○ Compiling / ...
 ✓ Compiled / in 2.2s (501 modules)
 ✓ Compiled in 108ms (235 modules)
Warning: Unknown Go version in /Users/mh4gf/ghq/github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs/api/go.mod
go: module . listed in go.work file requires go >= 1.21.3, but go.work lists go 1.18; to update it:
	go work use
go: module ../../../../api listed in go.work file requires go >= 1.21.3, but go.work lists go 1.18; to update it:
	go work use
Error: Command failed: go build -ldflags -s -w -o ./vercel-dev-server-go ./...
Hirotaka MiyagiHirotaka Miyagi

指示された通り go work use をやってみる

go work use
go: no go.work file found
	(run 'go work init' first or specify path using GOWORK environment variable)

エラーになったので go work init をしてみる

go work init
go work use

エラーはなさそうだ。こんな感じのファイルが生成された: https://github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs/commit/6c7278b3e44c0dd2748cf3c974e63adbd45b7593

もう一度 vercel dev を実行したが、goのdownloadが行われるようになったがまだエラーは出る

vercel dev
Vercel CLI 32.5.0
> Running Dev Command “next dev --port $PORT”
   ▲ Next.js 14.0.3
   - Local:        http://localhost:3000

> Ready! Available at http://localhost:3000
 ✓ Ready in 2.3s
Warning: Unknown Go version in /Users/mh4gf/ghq/github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs/go.mod
Downloading go: https://dl.google.com/go/go1.21.1.darwin-arm64.tar.gz
go: module . listed in go.work file requires go >= 1.21.3, but go.work lists go 1.18; to update it:
	go work use
Error: Command failed: go build -ldflags -s -w -o ./vercel-dev-server-go ./...
Hirotaka MiyagiHirotaka Miyagi

ちょっと横道に逸れるが、vercel dev の挙動が気になるので色々読んでみる。

vercel dev --debug でデバッグログを有効化して http://localhost:3000/api/index にアクセスする

> Ready! Available at http://localhost:3000
 ✓ Ready in 2.6s
> [debug] [2023-11-26T02:42:14.604Z] GET /api/index
> [debug] [2023-11-26T02:42:14.605Z] Reading `package.json` file
> [debug] [2023-11-26T02:42:14.605Z] Reading `vercel.json` file
> [debug] [2023-11-26T02:42:14.605Z] No `vercel.json` file present
> [debug] [2023-11-26T02:42:14.605Z] Locating files /Users/mh4gf/ghq/github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs
> [debug] [2023-11-26T02:42:14.605Z] Ignoring /Users/mh4gf/ghq/github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs/.git
> [debug] [2023-11-26T02:42:14.606Z] Ignoring /Users/mh4gf/ghq/github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs/.gitignore
> [debug] [2023-11-26T02:42:14.606Z] Ignoring /Users/mh4gf/ghq/github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs/.next
> [debug] [2023-11-26T02:42:14.606Z] Ignoring /Users/mh4gf/ghq/github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs/.vercel
> [debug] [2023-11-26T02:42:14.606Z] Ignoring /Users/mh4gf/ghq/github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs/node_modules
> [debug] [2023-11-26T02:42:14.606Z] Locating files /Users/mh4gf/ghq/github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs [1ms]
> [debug] [2023-11-26T02:42:14.610Z] Imported Builder "@vercel/go" from "/Users/mh4gf/.asdf/installs/nodejs/18.16.1/lib/node_modules/vercel/node_modules/@vercel/go"
> [debug] [2023-11-26T02:42:15.214Z] Proxying to "@vercel/go" dev server (port=65010, pid=86054)
> [debug] [2023-11-26T02:42:15.223Z] Killing builder dev server with PID 86054
> [debug] [2023-11-26T02:42:15.255Z] Killed builder dev server with PID 86054
  • "@vercel/go" をインポートしている。バンドラとのこと
  • "@vercel/go" dev serverを起動しプロキシしている?
  • bundler dev serverをkillしている

おそらくリクエストのたびにGoのハンドラをバンドル(≒ビルド?)し立ち上げている?

Hirotaka MiyagiHirotaka Miyagi

/vercel/cache というディレクトリに色々ファイルが生成されているので見てみる
( u6d1o35shmg のディレクトリは、似たようなディレクトリがたくさん作られている)

tree .vercel/cache
.vercel/cache
├── go
│   └── u6d1o35shmg
│       ├── api
│       │   ├── entrypoint.go
│       │   └── vercel-dev-server-main.go
│       ├── go.mod
│       └── go.work
└── golang -> /Users/mh4gf/Library/Caches/com.vercel.cli/golang/1.18.10_darwin_arm64

それぞれ生成されたファイルを見てみる

.vercel/cache/go/u6d1o35shmg/api/entrypoint.go
package main

import (
	"fmt"
	"net/http"
)

func Handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "<h1>Hello from Go!</h1>")
}
.vercel/cache/go/u6d1o35shmg/api/vercel-dev-server-main.go
package main

import (
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"strconv"
)

func main() {
	// create a new handler
	handler := http.HandlerFunc(Handler)

	// https://stackoverflow.com/a/43425461/376773
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		panic(err)
	}

	port := listener.Addr().(*net.TCPAddr).Port
	portBytes := []byte(strconv.Itoa(port))

	file := os.NewFile(3, "pipe")
	_, err2 := file.Write(portBytes)
	if err2 != nil {
		portFile := os.Getenv("VERCEL_DEV_PORT_FILE")
		os.Unsetenv("VERCEL_DEV_PORT_FILE")
		err3 := ioutil.WriteFile(portFile, portBytes, 0644)
		if err3 != nil {
			panic(err3)
		}
	}

	panic(http.Serve(listener, handler))
}
.vercel/cache/go/u6d1o35shmg/go.mod
module handler

go 1.21.3
require handler v0.0.0-unpublished
replace handler => ../../../../api/
.vercel/cache/go/u6d1o35shmg/go.work
use (
  .
)
  • entrypoint.goは自分が書いたGoのハンドラがコピーされているっぽい。moduleはmainになっている
  • vercel-dev-server-main.goはhttpのserveをしている
  • go.modでは、自分が書いたhandlerモジュールをreplaceしている
    - goのバージョンを下げたのに1.21.3なのはちょっと気になる?
  • go.workはuse (.) している
Hirotaka MiyagiHirotaka Miyagi

とりあえずローカルで動くところまでできたので、vercelにデプロイしてみる。が、エラーになる

vercel
Vercel CLI 32.5.0
🔍  Inspect: https://vercel.com/mh4gf/debug-vercle-serverless-function-golang-with-nextjs/FBkKFzSeZijN6CCbGC23uvgNwSxk [1s]
✅  Preview: https://debug-vercle-serverless-function-golang-with-nextjs-jqtx7hmar.vercel.app [1s]
Error: Command failed: go build -ldflags -s -w -o /tmp/6b30518c/handler /vercel/path0/api/main__vc__go__.go

vercelのビルドログを見てみる

Created all serverless functions in: 1.367s
Collected static files (public/, static/, .next/static): 3.665ms
Downloading go: https://dl.google.com/go/go1.18.10.linux-amd64.tar.gz
go: finding module for package github.com/vercel/go-bridge/go/bridge
go: downloading github.com/vercel/go-bridge v0.0.0-20221108222652-296f4c6bdb6d
go: found github.com/vercel/go-bridge/go/bridge in github.com/vercel/go-bridge v0.0.0-20221108222652-296f4c6bdb6d
go: finding module for package github.com/aws/aws-lambda-go/lambda
go: finding module for package github.com/aws/aws-lambda-go/events
go: downloading github.com/aws/aws-lambda-go v1.41.0
go: found github.com/aws/aws-lambda-go/events in github.com/aws/aws-lambda-go v1.41.0
go: found github.com/aws/aws-lambda-go/lambda in github.com/aws/aws-lambda-go v1.41.0
go: downloading github.com/stretchr/testify v1.7.2
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading github.com/pmezard/go-difflib v1.0.0
go: downloading gopkg.in/yaml.v3 v3.0.1
main__vc__go__.go:9:2: no required module provides package github.com/vercel/go-bridge/go/bridge: go.mod file not found in current directory or any parent directory; see 'go help modules'
main__vc__go__.go:8:2: package handler/handler is not in GOROOT (/vercel/path0/.vercel/cache/golang/src/handler/handler)
failed to `go build`
Error: Command failed: go build -ldflags -s -w -o /tmp/6b30518c/handler /vercel/path0/api/main__vc__go__.go

go.mod file not found in current directory or any parent directory なのでgo.modが見つけられていないということかな。
apiディレクトリ内に押しこめたのが原因か?ということでルートに持って行ってみる: https://github.com/MH4GF/debug-vercle-serverless-function-golang-with-nextjs/commit/2783b2a3dc3a36c062511ab1c91ac3f7fb187d33

それでも動かんな...