はじめに
OAuth認証で全リンクの本人確認を行うプロフィールリンクサービス「myna.me」を自社開発しました。Next.js 16 (App Router) + Auth.js v5 + Drizzle ORM + PostgreSQLの構成で、Railwayにデプロイしています。
本記事では、開発中にハマったポイントとその解決策を共有します。
技術構成
| カテゴリ | 技術 |
|---|---|
| フレームワーク | Next.js 16 (App Router) |
| 言語 | TypeScript |
| ORM | Drizzle ORM |
| DB | PostgreSQL |
| 認証 | Auth.js v5 (NextAuth) |
| スタイリング | Tailwind CSS v4 |
| ホスティング | Railway |
1. RailwayでSet-Cookieヘッダーが消える問題
症状
Auth.jsのマジックリンク認証で、ユーザーがメールのリンクをクリックした後、セッションcookieがセットされずにログインできない。
原因
Railwayのedge proxyが、307リダイレクトレスポンスからSet-Cookieヘッダーを剥がす場合があります。NextResponse.redirect() はデフォルトで307を返すため、cookieをセットしてからリダイレクトする一般的なパターンが動作しません。
解決策
HTMLレスポンス(ステータス200)でSet-Cookieヘッダーを返し、JavaScript + meta-refreshでリダイレクトするパターンに変更しました。
return new Response(
`<html><head>
<meta http-equiv="refresh" content="0;url=/dashboard">
</head><body>
<script>window.location.href='/dashboard'</script>
</body></html>`,
{
status: 200,
headers: {
'Content-Type': 'text/html',
'Set-Cookie': sessionCookie,
},
}
);
2. Railway上で req.url が内部URLを返す
症状
req.url を使ってリダイレクトURLを構築すると、http://0.0.0.0:8080/... のような内部URLになってしまう。
原因
Railway上ではNext.jsが内部ポートでリッスンしており、req.url はそのままの内部URLを返します。req.url.startsWith("https://") による本番判定も常に false になります。
解決策
リダイレクトURLには必ず環境変数 NEXT_PUBLIC_APP_URL を使用します。
// NG
const redirectUrl = new URL('/dashboard', req.url);
// OK
const redirectUrl = `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`;
3. Auth.js v5のJWE構造の理解
症状
ミドルウェアでJWTトークンのバリデーションが失敗する。
原因
Auth.js v5はJWS(署名JWT)ではなく**JWE(暗号化JWT)**を使用します。JWEは5つのパーツ(header..iv.ciphertext.tag)で構成され、2番目のパーツ(encrypted key)が常に空文字列になります。
parts.some(p => p.length === 0) のようなバリデーションでは必ず失敗します。
解決策
JWEの検証には decode() from next-auth/jwt を使用し、鍵導出にはcookie名をsaltとして指定します。
import { decode } from 'next-auth/jwt';
const token = await decode({
token: sessionToken,
secret: process.env.AUTH_SECRET,
salt: cookieName, // "__Secure-authjs.session-token"
});
4. Server Actionを使うページにはforce-dynamicが必須
症状
本番ビルド後にServer Actionを呼び出すと503エラーが返る。
原因
Next.jsの静的生成(○マーク)されたページはPOSTリクエストを処理できません。Server Actionは内部的にPOSTを使うため、静的ページでは動作しません。
解決策
Server Actionを使用するページには force-dynamic を追加します。
// app/dashboard/page.tsx
export const dynamic = "force-dynamic";
5. DBマイグレーションはビルド時に実行できない
症状
Railway上でビルドコマンドにDBマイグレーションを含めると、データベースに接続できずビルドが失敗する。
原因
Railwayのビルドフェーズでは内部ネットワーク(例: postgres.railway.internal)にアクセスできません。
解決策
マイグレーションはビルドコマンドではなく、Pre-deployコマンドで実行します。
# Railway設定
Build Command: npm run build
Pre-deploy Command: npx drizzle-kit migrate
まとめ
Railway + Next.js + Auth.jsの組み合わせは強力ですが、各技術のエッジケースにハマりやすいです。特にRailwayのedge proxyの挙動は公式ドキュメントにも明記されていない部分があるため、Set-Cookieが消える問題には注意が必要です。
この記事が同じ構成で開発している方の参考になれば幸いです。
myna.meは現在公開中です:https://myna.me