3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Next.js13 デプロイ時にAPIのパスを動的に変えるやり方

Posted at

経緯

Next.js13でAPIを取得したときにパスの関係で手こずったこと体験から、同じようなことを防ぐための備忘録。

目標

本番環境と開発環境でAPIのエンドポイントを動的に設定できるようにしたい。

環境

フロントエンド, バックエンド: Next.js
ホスティング: vercel

実践

よくある例

画面起動時にSSGやSSRでAPIを発火して取得することだと思っています。

以下の画面では環境変数API_URLhttp://localhost:3000を定義し、取得したデータをmap関数で展開しています。

app/bad/page.tsx
import Link from 'next/link';

export default async function Bad() {
  const API_URL = process.env.API_URL;
  const res = await fetch(`${API_URL}/api/bad`);
  const data: string[] = await res.json();

  return (
    <div className='mb-32 grid text-center lg:mb-0 lg:grid-cols-4 lg:text-left'>
      {data.map((item) => (
        <Link
          href='/'
          className='group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30'
          rel='noopener noreferrer'
          key={item}
        >
          <h2 className={`mb-3 text-2xl font-semibold`}>{item}</h2>
          <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>初期画面に戻ります</p>
        </Link>
      ))}
    </div>
  );
}
app/api/bad/route.ts
import { NextResponse } from 'next/server';

export const GET = async () => {
  const data = ['john', 'doe', 'alice', 'bob'];
  return NextResponse.json(data, { status: 200 });
};
.env.local
API_URL=http://localhost:3000

こちらをこのままvercelにデプロイしようとするとエラーになります。

TypeError: fetch failed
    at Object.fetch (node:internal/deps/undici/undici:11372:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  cause: Error: connect ECONNREFUSED 127.0.0.1:3000
      at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16)
      at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
    errno: -111,
    code: 'ECONNREFUSED',
    syscall: 'connect',
    address: '127.0.0.1',
    port: 3000
  }
}
Error occurred prerendering page "/bad". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: fetch failed
    at Object.fetch (node:internal/deps/undici/undici:11372:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
 ✓ Generating static pages (9/9) 
> Export encountered errors on following paths:
	/bad/page: /bad
Error: Command "npm run build" exited with 1

API_URLがlocalhost:3000のままなので当たり前なんですが、絶対パスではなく相対パスでビルドできるか試そうにも、そもそもローカル環境でビルドが通らないです。

GPT先生に聞いたところ以下の回答をもらった。

Next.jsでは、静的サイト生成(SSG)の際、ビルド時にページが事前にレンダリングされます。外部データを必要としないページについては、Next.jsが各ページごとに単一のHTMLファイルを生成します。
しかし、外部データに依存するページの場合、Next.jsはgetStaticPropsgetStaticPathsのような関数を提供しています。これらの関数はビルド時にデータを取得し、ページのpropsに渡して事前レンダリングするために使用されます。
getStaticPropsを使用する場合、この関数はビルド時に外部APIからデータを取得するために呼び出されます。
例として、絶対URLを使用してブログの投稿を取得するシナリオが示されています。同様に、pages/posts/[id].jsのような動的ルートの場合、getStaticPropsは特定の投稿のデータ(id)を使用して取得し、この関数もビルド時に呼び出されます。
これらの例と説明から、Next.jsにおけるSSGやSSRでは、特にビルド時にAPIからデータを取得する際には、絶対URLの使用が一般的であることがわかります。これは、サーバーサイドやビルド時のコンテキストでは相対URLが正しく解決されないためです。

他の記事も調べたところ、結論としてNext.jsでは絶対パスが好ましいことが分かった。

Mastering Next.js: Getting the Absolute URL in Nextjs Application

改善例

  • next/headersを使用して現在のURLを動的に取得する。
  • next/headersではプロトコル部分(http://)を取得できないため、環境変数で設定しておきデプロイ後https://に変更する。
app/good/page.tsx
import Link from 'next/link';
import { headers } from 'next/headers';
import { config } from '@/app/lib/config';

const fetchData = async (host: string) => {
  const res = await fetch(`${config.apiPrefix}${host}/api/good`);
  return res.json();
};

export default async function Good() {
  const host = headers().get('host');
  const data: string[] = await fetchData(host!);

  return (
    <div className='mb-32 grid text-center lg:mb-0 lg:grid-cols-4 lg:text-left'>
      {data.map((item) => (
        <Link
          href='/'
          className='group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30'
          rel='noopener noreferrer'
          key={item}
        >
          <h2 className={`mb-3 text-2xl font-semibold`}>{item}</h2>
          <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>初期画面に戻ります</p>
        </Link>
      ))}
    </div>
  );
}

以下は環境変数の読み込み用のlib/config.ts.env.local

開発環境ではAPI_PREFIX=http://と定義しておき、デプロイ時に環境変数を変更することで、開発環境と本番環境でコードを修正することがなくなる。

image.png

export const config = {
  apiPrefix: process.env.API_PREFIX,
};
API_PREFIX=http://

おわりに

参考にした記事など

Mastering Next.js: Getting the Absolute URL in Nextjs Application

Next.jsのapp routerを利用してNext.jsのAPIをlocalhost上で叩く時にURL parse Errorになるのを防ぐ - Qiita

Functions: headers

その他

使用したリポジトリ

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?