本ページはプロモーションが含まれています
お問い合わせ対応や社内ヘルプデスクの効率化を検討している方でLINEとAIの組み合わせに興味をお持ちの方も多いのではないでしょうか。
昨今盛り上がりを見せているDifyとLINEを連携すれば、人間に近い自然な言語でAIを活用したチャットボットを作成することができます。
DifyとLINEの連携にはDify公式プラグインが用意されていますが、色々と制約もあり、真に要求を満たすシステムはなかなか作りづらいのが現状です。
そこで今回は、プラグインを使用せず、DifyとLINEを連携するAPIサーバーを設けることで、より実用的なアーキテクチャを実装する方法をご紹介します。
LINEとDifyを連携させるメリット

まず最初にLINEとDifyを連携させるメリットから紹介させてください。
馴染みあるLINEからチャットベースでAIが利用可能
LINEであれば、日本人のほとんどが利用経験があるので、ユーザーに対して新しいアプリのインストールや操作学習を必要としません。
既存のインターフェースでそのままAIサービスを提供でき、導入ハードルを大幅に下げられます。
LLMを活用した人間に近い自然な会話が可能
DifyではChatGPTやGeminiなどの最新LLMを簡単に組み込めます。従来のルールベースのチャットボットと異なり、文脈を理解した自然で柔軟な対話が可能です。
複雑な質問や曖昧な表現にも適切に対応し、会話の流れを汲み取った回答を生成することができるうえ、人間のオペレーターと話すような感覚で対話できます。
RAGを用いた精度の高い回答の生成
DifyのRAG機能により、企業固有の知識ベースやFAQデータベースを参照した正確な回答を生成することができます。LLMの学習データだけでなく、最新情報や専門的な内容も回答に含められます。
製品マニュアルやサービス規約、社内手順書をナレッジベースに登録すれば、具体的で実用的な回答を提供可能。問い合わせ対応の品質向上と時間短縮を同時に実現できます。
LINEとDifyを連携する方法

LINEとDifyは直接データの受け渡しができません。
LINE Messaging APIはWebhookで外部サーバーにメッセージを送信し、Difyは用意されたAPIエンドポイントでメッセージを処理します。
異なる通信プロトコルとデータ形式を使用するため、中継サーバーが必要です。
LINE Messaging APIは、LINEプラットフォーム上でボットアプリケーションを構築するためのAPIです。ユーザーがボットにメッセージを送信すると、LINEサーバーが指定したWebhook URLにHTTP POSTリクエストを送信します。
基本的な仕組み
- Webhook受信:ユーザーメッセージを JSON 形式で受信
- 署名検証:リクエストがLINEからの正当なものかHMAC-SHA256で検証
- メッセージ処理:受信したメッセージに応じた処理を実行
- 返信送信:Reply APIまたはPush APIでユーザーに応答
主要なAPIエンドポイント
- Reply API:
https://api.line.me/v2/bot/message/reply
- Push API:
https://api.line.me/v2/bot/message/push
- 認証:Channel Access Tokenをヘッダーに設定
今回の実装では、Webhookでメッセージを受信し、Reply APIで応答する基本パターンを使用します。
LINE BotのDifyプラグインを使用する
最も手軽なのは、Dify公式のLINE Botプラグインです。複雑な中継サーバーの構築が不要で、GUI設定だけでLINEとDifyを連携できます。
LINEのChannel Access TokenとChannel Secretをプラグイン設定画面に入力するだけで連携完了。開発コスト抑制と素早い導入に最適です。
中継用のAPIサーバーを実装する
より柔軟で拡張性の高いシステムには、独自の中継サーバー実装があります。
LINE Webhookを受信し、データを加工してDify APIに送信、レスポンスをLINEに返す処理を自前実装します。
AWS Lambda、Google Cloud Functions、GAS、Laravel、Flaskなど様々な技術スタックで実装可能。企業固有要件に合わせたカスタマイズや他システム連携も容易に実現できます。
Difyプラグインを使う場合と使わない場合の違い
LINE Botプラグインのメリット・デメリット
メリット | デメリット |
---|---|
設定が簡単で導入が早い 技術知識が少なくても短時間で連携可能 Dify公式サポートあり | カスタマイズ制約あり 企業固有要件(認証連携、詳細ログ、外部 DB 連携など)への対応困難 外部依存によるリスク |
プラグインなしでAPIサーバーを作るメリット・デメリット
メリット | デメリット |
---|---|
システムの完全制御が可能 メッセージ受信の前処理、Dify送信前のデータ加工、レスポンス後処理など、あらゆる段階でカスタムロジック挿入可能 Slack、Microsoft Teams との連携、複数 AI サービス使い分け、既存システム(CRM、データベース)統合も容易 詳細ログ記録、パフォーマンス監視、エラーハンドリングも可能 | 開発・運用コストが高くなる 署名検証、エラーハンドリング、セキュリティ対策などを全て自前で実装する必要がある |
APIサーバーのシステム構成
今回の記事では、Next.js API Routesを中核とした以下のアーキテクチャを採用しています。
┌──────────────────────────────────────┐
│ LINE Messaging API │
│ (Webhook → your-domain.com) │
└───────────────┬──────────────────────┘
│ HTTPS POST /api/line-webhook
│ Header: x-line-signature
▼
┌──────────────────────────────────────┐
│ Next.js API Server │
│ - ポート 3000 (本プロジェクト) │
│ - /api/line-webhook エンドポイント │
│ - LINE Bot SDK で受信処理 │
│ - 署名検証 (HMAC-SHA256) │
└───────────────┬──────────────────────┘
│ HTTPS API呼び出し
│ Authorization: Bearer {DIFY_API_KEY}
▼
┌──────────────────────────────────────┐
│ Dify AI Platform │
│ - server-choice-demo.site/v1 │
│ - POST /chat-messages │
│ - チャットボット AI処理 │
└───────────────┬──────────────────────┘
│
│ (会話履歴・文脈保存)
▼
┌──────────────────────────────────────┐
│ Supabase Database │
│ - conversation_meta テーブル │
│ - conversations テーブル │
│ - 会話ID・メッセージ履歴管理 │
└──────────────────────────────────────┘
この構成により、LINEからのメッセージをNext.jsのAPIエンドポイントで受信し、必要な処理を行った後に Dify APIに転送します。
Difyからのレスポンスを受け取った後は、再びNext.jsで加工処理を行い、最終的にLINE Messaging APIを通じてユーザーに返信するという流れになります。
主要ライブラリと依存関係
実装には以下のライブラリを使用します。
@line/bot-sdk
: LINE Messaging APIとの連携axios
: Dify APIへのHTTPリクエスト送信@supabase/supabase-js
: 会話履歴の永続化crypto
: LINE Webhook署名検証
環境構築と基本設定
Next.jsアプリの実装に入る前に、必要な環境構築と各サービスの基本設定を行います。
Difyのインストール
Difyのインストールがまだの方は、先にインストールを済ませてください。
今回はLINEのお問い合わせを24時間受け付ける必要があるので、VPSを借りてそこにDifyをインストールしています。
Difyのセルフホストはインストールそのものは簡単なのですが、ドメイン設定やSSL周りがやや面倒なので、今回はその辺を丸っとワンストップでやってくれるXServer VPSを利用しました。

Next.js開発環境の準備
Next.jsプロジェクトの初期化
今回はNext.jsのAPI RoutesでAPIサーバーを作成します。ターミナルから以下のコマンドを実行してください。
npx create-next-app@latest dify-line-bot
cd dify-line-bot
TypeScript + App Routerで作ります。この辺は好みか要件に合わせて自由にしてもらって結構です。
必要なライブラリのインストール
続いて、実装に必要なライブラリをインストールします。
npm install @line/bot-sdk axios @supabase/supabase-js
npm install -D @types/node
環境設定ファイルの作成
各サービスのAPIキーやURLを管理する環境設定ファイル(.env)を用意します。
touch .env
以下で各APIとURLの取得手順を順に解説してきます。
# LINE Messaging API
LINE_CHANNEL_SECRET=xxxxx
LINE_CHANNEL_ACCESS_TOKEN=xxxxx
# Dify
DIFY_API_KEY=xxxxx
DIFY_BASE_URL=XXXX
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_PUVLISHABLE_KEY=xxxxx
LINE公式アカウントの作成
まだ公式アカウントを作成していない方はアカウントを開設しましょう。
LINE公式アカウントを作成するには、LINEの公式アカウントを作成するページに遷移します。
「LINE公式アカウントをはじめる」ボタンをクリックします。

LINEアカウントで登録もしくはメールアドレスで登録の2つから選べます。お好きな方を選択して登録を進めてください。

メールアドレスで登録する場合、登録したメールアドレス宛にアカウント登録用リンクが送付されます。

「名前」と「パスワード」を入力後、「登録」をクリックしてください。

確認画面→SMS認証と済ませていくと、LINE公式アカウントのアカウント情報を入力することができます。
必要事項を入力してください。

確認画面を経て、問題がなければLINE公式アカウントが正常に作成されます。

LINE Messaging API設定
LINE Developersアカウントの作成
LINE Messaging APIの取得にはLINE Developersアカウントへの登録が必要です。以下の手順でアカウントを作成してください。
LINE Developersページにアクセスします。
右上に「コンソール」と表示されるので、クリックしてください。

LINE公式アカウントと同じように必要事項を入力してアカウントを作成します。

Channelの作成とAccess Tokenを取得
LINE公式アカウントとDeloperコンソールへの登録が完了できたら、次はMessaging APIを有効化していきます。
以下の「LINE Official Account Manager」ページにアクセスしてください。
アクセスすると、先ほど作成したアカウントが表示されますので、そのアカウントを選択します。

アカウント選択後、画面右上の「設定」ボタンを押します。設定画面に移動したら、メニュー左側の「Messaging API」をクリックしてください。

「Messaging APIを利用する」ボタンが表示されますので、こちらをクリックします。

すると、先ほどLINE Developersで作成したプロバイダーが表示されますので、チェックをつけて「同意する」ボタンを押します。

続いて、プロバイダーのプライバシーポリシーと利用規約を登録する画面が表示されます。
こちらは必要に応じて設定していただければと思いますが、後からでも変更が可能ですので、今回はそのまま「OK」ボタンを押して進めてください。

確認画面が表示されますので、こちらでも「OK」ボタンを押しましょう。
ここまでの手順で、「Channel ID」と「Channel Secret」が作成されます。これらの情報は後ほど実装で利用しますので、コピーして大切に保管しておきましょう。

次に、チャネルアクセストークンを取得します。
公式アカウントの管理画面(https://developers.line.biz/console/)に戻り、作成したプロバイダーを選択してください。

先ほど登録した「Messaging API」をクリックします。

ページを一番下までスクロールすると、「チャネルアクセストークン」の項目が表示されますので、「発行」ボタンを押します。

アクセストークンが発行されます。
ここまでの手順で「チャネルアクセストークン」と「チャネルシークレット」を発行することができました。
この2つをNext.jsアプリに作成した環境設定ファイル(.env)に設定しましょう。
# LINE Messaging API
# ここに設定する
LINE_CHANNEL_SECRET=xxxxx
LINE_CHANNEL_ACCESS_TOKEN=xxxxx
# Dify
DIFY_API_KEY=xxxxx
DIFY_BASE_URL=XXXX
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_PUVLISHABLE_KEY=xxxxx
Webhook URLの準備
Messaging APIとデータの受け渡しをするエンドポイントを指定します。
LINE Developer Consoleの「Messaging API設定」タブを開いて、Webhook設定にエンドポイントとするURLを入力してください。
今回は/api/line-webhook
をエンドポイントとして指定しました。

Difyセットアップ
上記の「Difyインストール」でDifyのインストールから管理者アカウントの作成までは完了していると思います。
ここでは、Difyアプリの作成からAPIキーの取得までのセットアップを行っていきます。
Difyアプリの作成
Next.jsアプリから送信されたLINEメッセージデータを処理するチャットフローを作成していきます。
まだアプリを作成していない方はDifyのダッシュボードから作成していきましょう。
「チャットフロー」を選択し、「アプリ名」と「説明」を記入して作成してください。

今回は簡易的なものなので、フロー自体はデフォルトのまま使用します。LLMノードの各種設定を行いましょう。
私の場合は以下のようにしています。

設定項目 | 設定値 |
---|---|
AIモデル | Gemini 2.5 Pro |
コンテキスト | sys.query |
SYSTEM | あなたはECサイトのサポートスタッフです。 ユーザーからのお問い合わせに対して、丁寧かつ分かりやすく回答してください。 ルール: – 回答は日本語で行ってください。 – 主な役割は、商品の問い合わせ、注文状況の確認、返品・交換、店舗情報の案内などを行うことです。 – よくある質問に対しては簡潔に即答してください。 – 複雑な問い合わせや判断が必要な内容は「サポート担当者に確認します」と案内してください。 – 個人情報(パスワードやクレジットカード番号)は入力しないように注意喚起してください。 – 文章は丁寧語(です・ます調)を使ってください。 |
プロンプトの設定では、LINE Botの用途に合わせてシステムメッセージを調整してください。
例えば、カスタマーサポート用途であれば「あなたは親切で丁寧なカスタマーサポートです。お客様の質問に分かりやすくお答えしてください。」といった指示を設定します。
DifyアプリのAPIキーを取得
続いて、Next.jsアプリと連携するために、DifyアプリのAPIキーを発行します。
作成したチャットフローの設定画面の左メニューにある「APIアクセス」をクリックしてください。
「ベースURL」が表示されるので、こちらをNext.jsの環境設定ファイル(.env)に記述してください。

次に、右上の「APIキー」をクリックします。

「新しいシークレットキーを作成」をクリックして、シークレットキーを発行します。

シークレットキーをNext.jsの環境設定ファイル(.env)に設定してください。
# LINE Messaging API
LINE_CHANNEL_SECRET=xxxxx
LINE_CHANNEL_ACCESS_TOKEN=xxxxx
# Dify
# ここに設定する
DIFY_API_KEY=xxxxx
DIFY_BASE_URL=XXXX
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_PUVLISHABLE_KEY=xxxxx
Supabaseデータベース設定
今回は会話履歴を保存するためのデータベースとして、Supabaseを使用しました。ここはMySQLでもなんでも問題ありません。
テーブル設計(conversation_meta, conversations)
会話の継続性を保持するため、2つのテーブルを作成します。
-- conversation_meta テーブル
CREATE TABLE conversation_meta (
user_id TEXT PRIMARY KEY,
conversation_id TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- conversations テーブル
CREATE TABLE conversations (
id SERIAL PRIMARY KEY,
user_id TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
message TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
conversation_meta
テーブルは LINEユーザーIDとDifyのconversation_id
の紐付けを管理し、conversations
テーブルは会話履歴を保存します。
接続設定
Next.jsのAPIサーバーと接続するためにSupabaseのプロジェクトURLとAPI Keyを取得します。
作成したSupabaseプロジェクトの左メニューにある「Project Settings」 > 「API Keys」の「API Keys」タブからPublishable Key
をNext.jsの環境設定ファイル(.env)に設定します。

続いて、「Data API」から「Project URL」をNext.jsの環境設定ファイル(.env)に設定します。

以上で実装に必要な事前設定が完了です。
中継サーバー(Next.js API Routes)の実装
いよいよ中継サーバーの実装に入ります。この章では、LINEからのWebhookを受信し、Dify APIと連携して応答を返すまでの一連の処理を実装します。
Webhookエンドポイントの実装
まず、Next.jsの src/app/
ディレクトリに/api/line-webhook/
ディレクトリを作成し、そこにroute.ts
ファイルを作成します。
.
├── api
│ └── line-webhook
│ └── route.ts
├── favicon.ico
├── globals.css
├── layout.tsx
├── page.tsx
└── pages
今回はroute.tsに全ての処理を書いちゃってますが、もし参考にされる方はよしなにディレクトリ分けして使ってください。
route.tsの全体像
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
import { Client } from '@line/bot-sdk';
import axios from 'axios';
import { createClient } from '@supabase/supabase-js';
// 環境変数の定義
const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET!;
const LINE_CHANNEL_ACCESS_TOKEN = process.env.LINE_CHANNEL_ACCESS_TOKEN!;
const DIFY_API_KEY = process.env.DIFY_API_KEY!;
const DIFY_BASE_URL = process.env.DIFY_BASE_URL!;
// LINE Bot クライアントの初期化
const client = new Client({
channelAccessToken: LINE_CHANNEL_ACCESS_TOKEN,
});
// Supabase クライアントの初期化
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!
);
// 署名検証関数
function verifySignature(body: string, signature: string): boolean {
const expectedSignature = crypto
.createHmac('SHA256', LINE_CHANNEL_SECRET)
.update(body, 'utf8')
.digest('base64');
return signature === `SHA256=${expectedSignature}`;
}
// Dify API 呼び出し関数
async function callDifyAPI(message: string, conversationId?: string) {
const requestData = {
inputs: {},
query: message,
response_mode: 'blocking',
conversation_id: conversationId || '',
user: 'line-user'
};
const response = await axios.post(
`${DIFY_BASE_URL}/chat-messages`,
requestData,
{
headers: {
'Authorization': `Bearer ${DIFY_API_KEY}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
}
// 会話ID取得関数
async function getConversationId(userId: string): Promise<string | null> {
const { data, error } = await supabase
.from('conversation_meta')
.select('conversation_id')
.eq('user_id', userId)
.single();
if (error || !data) {
return null;
}
return data.conversation_id;
}
// 会話ID保存関数
async function saveConversationId(userId: string, conversationId: string) {
await supabase
.from('conversation_meta')
.upsert({
user_id: userId,
conversation_id: conversationId,
updated_at: new Date().toISOString()
});
}
// 会話履歴保存関数
async function saveConversation(userId: string, role: string, message: string) {
await supabase
.from('conversations')
.insert({
user_id: userId,
role: role,
message: message,
created_at: new Date().toISOString()
});
}
// エラーハンドリング関数
async function handleError(error: any, userId: string, replyToken: string) {
console.error('Error in handleTextMessage:', error);
await client.replyMessage(replyToken, {
type: 'text',
text: '申し訳ございません。現在システムに問題が発生しております。しばらく時間をおいて再度お試しください。'
});
}
// テキストメッセージ処理関数
async function handleTextMessage(userId: string, messageText: string, replyToken: string) {
try {
// 1. 既存の会話IDを取得
const conversationId = await getConversationId(userId);
// 2. Dify API を呼び出し
const difyResponse = await callDifyAPI(messageText, conversationId);
// 3. 会話IDを保存/更新
if (difyResponse.conversation_id !== conversationId) {
await saveConversationId(userId, difyResponse.conversation_id);
}
// 4. 会話履歴を保存
await Promise.all([
saveConversation(userId, 'user', messageText),
saveConversation(userId, 'assistant', difyResponse.answer)
]);
// 5. LINE に返信
await client.replyMessage(replyToken, {
type: 'text',
text: difyResponse.answer
});
console.log(`Response sent to user ${userId}: ${difyResponse.answer}`);
} catch (error) {
await handleError(error, userId, replyToken);
}
}
// メインのPOST処理関数
export async function POST(request: NextRequest) {
try {
const body = await request.text();
const signature = request.headers.get('x-line-signature');
// 署名検証
if (!signature || !verifySignature(body, signature)) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
// イベント処理
const events = JSON.parse(body).events;
for (const event of events) {
if (event.type === 'message' && event.message.type === 'text') {
const userId = event.source.userId;
const messageText = event.message.text;
const replyToken = event.replyToken;
// テキストメッセージを処理
await handleTextMessage(userId, messageText, replyToken);
}
}
return NextResponse.json({ status: 'ok' });
} catch (error) {
console.error('Webhook error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
以下で各処理について簡単に解説します。
署名検証機能
verifySignature
関数では、LINEからのWebhookが正当なものかどうかを検証しています。
HMAC-SHA256を使用した署名検証により、LINE以外からの不正なリクエストをブロックし、セキュリティを確保します。この検証はWebhookの改ざんを防ぐ重要な仕組みです。
function verifySignature(body: string, signature: string): boolean {
const expectedSignature = crypto
.createHmac('SHA256', LINE_CHANNEL_SECRET)
.update(body, 'utf8')
.digest('base64');
return signature === `SHA256=${expectedSignature}`;
}
Dify API クライアント機能
callDifyAPI
関数では、Dify APIとの通信を行います。response_mode: 'blocking'
を指定することで、Difyからの完全な応答を待ってからレスポンスを受け取ります。これにより、ストリーミング配信ではなく一括での応答を実現しています。
async function callDifyAPI(message: string, conversationId?: string) {
const requestData = {
inputs: {},
query: message,
response_mode: 'blocking',
conversation_id: conversationId || '',
user: 'line-user'
};
const response = await axios.post(
`${DIFY_BASE_URL}/chat-messages`,
requestData,
{
headers: {
'Authorization': `Bearer ${DIFY_API_KEY}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
}
会話履歴管理機能
会話の継続性を保つために、以下の3つの関数で会話情報を管理しています。
async function saveConversation(userId: string, role: string, message: string) {
await supabase
.from('conversations')
.insert({
user_id: userId,
role: role,
message: message,
created_at: new Date().toISOString()
});
}
getConversationId
-
ユーザーごとの
conversation_id
を取得 saveConversationId
-
新しい
conversation_id
を保存・更新 saveConversation
-
ユーザーとAIの会話内容を履歴として保存
これらの実装により、ユーザーが過去の会話を参照した質問をしても適切に応答できます。
統合処理フロー
handleTextMessage
関数では、メッセージ受信から返信までの一連の処理を統合しています。
既存の会話IDの取得、Dify APIの呼び出し、会話IDの保存・更新、会話履歴の保存、LINEへの返信という5つのステップを順次実行することで、一連の対話システムを実現しています。
async function handleTextMessage(userId: string, messageText: string, replyToken: string) {
try {
// 1. 既存の会話IDを取得
const conversationId = await getConversationId(userId);
// 2. Dify API を呼び出し
const difyResponse = await callDifyAPI(messageText, conversationId);
// 3. 会話IDを保存/更新
if (difyResponse.conversation_id !== conversationId) {
await saveConversationId(userId, difyResponse.conversation_id);
}
// 4. 会話履歴を保存
await Promise.all([
saveConversation(userId, 'user', messageText),
saveConversation(userId, 'assistant', difyResponse.answer)
]);
// 5. LINE に返信
await client.replyMessage(replyToken, {
type: 'text',
text: difyResponse.answer
});
console.log(`Response sent to user ${userId}: ${difyResponse.answer}`);
} catch (error) {
await handleError(error, userId, replyToken);
}
}
VPSへNext.js APIサーバーをデプロイ
作成したNext.js APIサーバーをデプロイします。今回はDifyをデプロイしているVPSにNext.jsも一緒にデプロイしてしまいます。
XServer VPSのDifyテンプレートを使うと、docker-composeを使った方式でDifyがデプロイされているので、Next.jsもコンテナ化してデプロイすることにしました。
この辺りのインフラ設計はみなさん悩まれるところだと思います。ご自身のリソースや要件に合わせて変更していただければと思います。
Next.jsアプリフォルダをアップロード
Next.jsのプロジェクトフォルダをVPSにアップロードします。
ビルドしたものをアップしても良いのですが、VPSのdocker起動時にビルド処理をさせるDockerfileを作成しました。
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Run
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/next.config.s ./
COPY --from=builder /app/public ./public
EXPOSE 3001
CMD ["npm", "start"]
アップロード先は任意のディレクトリで良いですが、私はXServer VPSがDifyを/root/
ディレクトリに置いていたので、同じ階層に/line-dify/
ディレクトリを作成し、そこに設置しました。
tree /root/line-dify/
/root/line-dify/
├── Dockerfile # Next.jsプロジェクトフォルダにDockerfileを作る
├── README.md
├── eslint.config.mjs
├── next-env.d.ts
├── next.config.ts
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
│ ├── file.svg
│ ├── globe.svg
│ ├── next.svg
│ ├── vercel.svg
│ └── window.svg
├── src
│ └── app
│ ├── api
│ │ └── line-webhook
│ │ └── route.ts
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.tsx
│ └── pages
└── tsconfig.json
.envファイルをもこのNext.jsプロジェクトフォルダに置きます。
# LINE Messaging API
LINE_CHANNEL_SECRET=xxxxx
LINE_CHANNEL_ACCESS_TOKEN=xxxxx
# Dify
DIFY_API_KEY=xxxxx
DIFY_BASE_URL=XXXX
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_PUVLISHABLE_KEY=xxxxx
docker-compose.override.yamlを作成
Next.jsコンテナを作成するために、docker-compose.override.yamlファイルを作成します。
既存のdocker-compose.yamlを編集しても良いのですが、Difyのアップデート時に上書きされてしまう可能性があるため、docker-compose.override.yamlを作成しています。
まずは、Difyのプロジェクトディレクトリに入ります。XServer VPSの場合は/root/dify/
ですね。
cd /root/dify/
docker composeを使ってDifyをセルフホストした場合、dify
ディレクトリにdocker
ディレクトリが作成されています。そこにdocker-compose.override.yamlを作成しましょう。
cd docker
vim docker-compose.override.yaml
中身を以下のようにします。
services:
certbot:
restart: always
nginx:
volumes:
- ./nginx/custom.conf.template:/etc/nginx/custom.conf
nextjs:
build: ../../line-dify-bot
container_name: nextjs
ports:
- "3001:3000"
env_file:
- ../../line-dify/.env
depends_on:
- api
networks:
- ssrf_proxy_network
- default
SSL証明書管理のcartbotはXServer VPS側が記述しているものなので、下に続く形でNginxとNext.jsのコンテナの設定を上書きしています。
3000番ポートはDifyのWebコンテナが使っているので、Next.jsの方は3001にしています。
Nginxのルーティングを設定
NgixのコンテナはDifyがデフォルトで用意しているので、そこにNext.jsアプリへのルーティングを追加します。
ただ、そのままdefault.confを編集すると、こちらもDifyのアップデート時に上書きされる可能性があるので、別途confファイルを作成して、includeするようにします。
docker
ディレクトリにnginx
ディレクトリがあるので、移動して、custom.conf.template
を作成します。
cd nginx
vim custom.conf.template
中身を以下のようにします。
location = /api/line-webhook {
proxy_pass http://nextjs:3000;
include proxy.conf;
}
/dify/docker/nginx/conf.d/
のdefault.conf.template
を編集して、custom.conf
をincludeするようにします。
# Please do not directly edit this file. Instead, modify the .env variables related to NGINX configuration.
server {
listen ${NGINX_PORT};
server_name ${NGINX_SERVER_NAME};
location /console/api {
proxy_pass http://api:5001;
include proxy.conf;
}
# custom.confのlocationをinclude
include /etc/nginx/custom.conf;
location /api {
proxy_pass http://api:5001;
include proxy.conf;
}
location /v1 {
proxy_pass http://api:5001;
include proxy.conf;
}
location /files {
proxy_pass http://api:5001;
include proxy.conf;
}
location /explore {
proxy_pass http://web:3000;
include proxy.conf;
}
location /e/ {
proxy_pass http://plugin_daemon:5002;
proxy_set_header Dify-Hook-Url $scheme://$host$request_uri;
include proxy.conf;
}
location / {
proxy_pass http://web:3000;
include proxy.conf;
}
location /mcp {
proxy_pass http://api:5001;
include proxy.conf;
}
# placeholder for acme challenge location
${ACME_CHALLENGE_LOCATION}
# placeholder for https config defined in https.conf.template
${HTTPS_CONFIG}
}
このままでは、Difyのアップデート時にincludeが上書きされて消えてしまう可能性があるので、アップデート時に自動でinclude文を追記するようにシェルスクリプトを用意しました。
XServer VPSがDifyの更新用スクリプトupdate.sh
を用意してくれていたので、今回はこちらにinclude文を追記するスクリプトを追加することにします。
vim /root/update.sh
#!/bin/bash
dify_dir="/root/dify/docker"
ENV_FILE="/root/dify/docker/.env"
ENV_EXAMPLE_FILE="/root/dify/docker/.env.example"
ENV_TMP_FILE="/root/dify/docker/.env.tmp"
TIMESTAMP=$(date "+%Y%m%d_%H%M%S")
# dockerサービス停止とコンテナ削除
cd ${dify_dir}
docker compose --profile certbot down
# dify最新ソースコード取得
cd ${dify_dir}
git pull origin main
# .env更新
cp -p ${ENV_FILE} /root/backup_dify_env/${TIMESTAMP}.env
cp -p ${ENV_EXAMPLE_FILE} ${ENV_TMP_FILE}
sed '/^#/d; /^\s*$/d' "${ENV_FILE}" | while IFS= read -r line; do
KEY=$(echo "${line}" | cut -d'=' -f1)
VALUE=$(echo "${line}" | cut -d'=' -f2)
if [[ -n "${VALUE}" ]]; then
sed -i "s|^${KEY}=.*|${KEY}=${VALUE}|" "${ENV_TMP_FILE}"
fi
done
# 更新された .env.tmp を .env にコピー
cp -pf "${ENV_TMP_FILE}" "${ENV_FILE}"
# .env.tmpを削除
unlink "${ENV_TMP_FILE}"
# default.conf.templateにcustom.confのincludeを自動追加
DEFAULT_CONF="${dify_dir}/nginx/conf.d/default.conf.template"
CUSTOM_INCLUDE_LINE=" include /etc/nginx/custom.conf;"
# serverブロックの直前の '}' の前にincludeを挿入(なければ追加)
if ! grep -qF "$CUSTOM_INCLUDE_LINE" "$DEFAULT_CONF"; then
# '}' の直前に挿入
sed -i "/^}/i $CUSTOM_INCLUDE_LINE" "$DEFAULT_CONF"
echo "custom.confのincludeをdefault.conf.templateに追加しました。"
else
echo "custom.confのincludeは既に追加されています。"
fi
# dockerサービス構築・起動
cd ${dify_dir}
docker compose --profile certbot up -d
Dockerを再起動させる
Dockerのコンテナな環境を変更したので、一度Dockerを再起動させます。
docker compose restart
全てのコンテナが正常に起動しているか確認してください。
docker compose ps
問題なく起動していれば、これで一通りデプロイ作業は終了です。お疲れ様でした。
デプロイしたDifyアプリを使ってみる
では、さっそくデプロイしたLINEと連携させたDifyアプリを使ってみましょう。
LINE公式アカウントに何か質問してみてください。違和感のない自然な言語で返答が返ってくると思います。

まとめ
実装したアーキテクチャの振り返り
今回の記事では、DifyプラグインのLINE Botを使用せず、Next.js API Routesを中継サーバーとしたインテグレーション層を設けることで、LINE Messaging APIとDifyを連携させる方法を解説しました。
公式プラグインでは実現困難な企業固有の要件(詳細なログ記録、カスタム認証、外部システム連携など)にも柔軟に対応できそうです。
今後の拡張可能性
今回構築した中継サーバーの仕組みは、LINE 以外のプラットフォームにも応用できます。Slack、Microsoft Teams、Discord などの Webhook を受け取るエンドポイントを追加することで、マルチプラットフォーム対応の AI チャットボットシステムを構築可能です。
以上で、Next.js API Routes を活用した Dify と LINE の連携システムの構築が完了しました。この記事で紹介したアーキテクチャパターンを参考に、皆さんの企業やプロジェクトに最適なチャットボットシステムを構築していただければ幸いです。