Next.js ( Page Router )でLINE Message APIを使ったbotを作った時の覚書
LINE向けのbotをNext.jsとVercelで開発・デプロイ。技術選定や実装の背景、LINE Message APIの利用方法を解説。Webhookを処理するための関数や、push送信の方法も紹介。無料枠が減少する中、replyMessageでの処理を重視し、予算やユースケースに合わせて検討。LIFFアプリやリッチメニューの活用も提案。将来的にはAWS LambdaやCloudflare Workersを利用することも検討。要件の変更に伴い、再度記事化予定。
目次
LINE向けのbotを作った際、Next.jsをVercelを利用して開発・デプロイしたので、その時の振り返りを兼ねた記事を作りました。LINEDC Advent Calendar 2024の3日目として投稿しています。
背景や技術選定について
個人的にお手伝いしているプロジェクトで、LINE botを作る機会がありました。Next.js App Routerがリリースされてまもない時期でしたので、手慣れた方法で開発することを優先し、Next.js Page Routerを採用、それにあわせてデプロイ先についてもVercelとしました。優先順位として、できるだけ低コストかつ手がかからない形での開発である必要があったため、手慣れたツールとサービスを利用することを優先したという形です。
Next.jsでLINE Message APIを利用する
LINE botはWebhook APIを作る形で実装します。そのため、LINEから送信されたリクエストであることを検証する処理が必要です。当時リリースされていたbot SDKはasyncに対応していなかったため、このようなラッパーを作っています。
import { NextApiRequest, NextApiResponse } from 'next'
import { middleware as lineMiddleware, WebhookEvent } from '@line/bot-sdk'
const runMiddleware = async (
req: NextApiRequest,
res: NextApiResponse,
fn: (...args: any[]) => void,
) => {
return new Promise((resolve, reject) => {
fn(req, res, (result: any) => (result instanceof Error ? reject(result) : resolve(result)))
})
}
export type LineMessageWebhookCallback = (event: WebhookEvent) => Promise<void>
export const handleLINEMessageWebhook = async (
req: NextApiRequest,
res: NextApiResponse,
callback: LineMessageWebhookCallback,
) => {
try {
if (req.method?.toLocaleLowerCase() === 'post') {
const middleware = lineMiddleware(process.env.LINE_SECRET)
await runMiddleware(req, res, middleware)
await Promise.all(
req.body.events.map(async (event: WebhookEvent) => {
await callback(event)
}),
)
}
res.status(200).end()
return
} catch (e) {
console.log(e)
res.status(500).json(e as any)
}
}
handleLINEMessageWebhook
関数を利用して、処理を実装します。
export const config = {
api: {
bodyParser: false,
},
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await handleLINEMessageWebhook(req, res, async (event) => {
console.log(event)
if (event.mode !== 'active') return
if (event.type === 'follow') {
return
}
if (event.type === 'message') {
// @TODO 返信メッセージを実装
あとはbot-sdk
で返信またはpush送信を判定する処理を作って、処理内で実行します。
export const pushMessage = async ({
text,
replyToken,
lineUserId,
}: {
text: string
replyToken?: string
lineUserId?: string
}): Promise<void> => {
const message: Message = {
type: 'text',
text,
}
if (replyToken) {
await client.replyMessage(replyToken, message)
} else if (lineUserId) {
await client.pushMessage(lineUserId, message)
} else {
throw new Error('replyToken or lineUserId is required')
}
}
実装してみて
Message APIはpush送信件数の無料枠が年々減少しています。そのため、できるだけreplyMessage
で処理を返す・完結できる形を目指しつつ、あとは予算とユースケースに相談するような形が、個人開発や小規模案件には向いているかなと思います。場合によってはLIFFアプリを実装し、リッチメニューからタップでGUIを表示する形で情報提供することも検討すると、よりコスト削減が期待できるかもしれません。
当時はNext.js Page Routerを使いましたが、今やるならHonoをAWS LambdaかCloudflare Workersにデプロイして使うかも・・・?と思います。ちょっと作り直したい要件も出てきてはいますので、そのあたりがひと段落したらまた記事化します。