Open3

Next.js Server Only機能

MAAAAAAAAAAAMAAAAAAAAAAA

サーバーサイドのみの実行

Next.js 14 では、サーバー上でのみ実行すべきコードに import 'server-only'; とマークすることができます。これにより、クライアントコンポーネントがこのモジュールをインポートしようとすると、ビルドエラーが発生します。

この機能は、機密コードや内部ビジネスロジックをクライアントに誤って漏洩させないようにするのに役立ちます。

使用例:
機密APIキーや顧客データの処理ロジックをサーバーサイドのみで実行したい場合など。

// server.js
import 'server-only';
import secretKey from './secrets';

export function fetchData() {
  // secretKeyを使ってデータを取得
}

データのクライアントへの渡し方

データをクライアントに渡す主な方法は、Propsをクライアントコンポーネントに渡すときに自動的に行われるReact Server Componentsを使うことです。このシリアル化はJSONのスーパーセットをサポートしますが、カスタムクラスは転送できません。

使用例:
APIから取得したユーザー データをクライアントに渡したい場合。

// page.jsx
import { getUserData } from './userData';

export default async function Page({ params }) {
  const user = await getUserData(params.id);
  return <UserProfile user={user} />
}

大きすぎるオブジェクトがクライアントに漏洩するのを防ぐには、データアクセスレコードとしてクラスを使うのが良いトリックです。

実験的な Taint API

Next.js 14では、実験的なReact Taint APIを試すこともできます。next.config.jstaintフラグを有効にします。

// next.config.js
module.exports = {
  experimental: {
    taint: true,
  }
}

これにより、クライアントに渡すことを許可しないオブジェクトにマークを付けることができます。

使用例:
機密のユーザーデータをクライアントに渡さないようにする場合。

// data.ts
import { experimental_taintObjectReference } from 'react';

export async function getUserData(id) {
  const data = /* APIから取得したユーザーデータ */;
  experimental_taintObjectReference('Do not pass user data to the client', data);
  return data;
}

// page.tsx 
import { getUserData } from './data';

export async function Page({ searchParams }) {
  const userData = getUserData(searchParams.id); 
  return <ClientComponent user={userData} /> // エラー
}

さらにexperimental_taintUniqueValueを使うと、トークンなどの一意の文字列値もブロックできます。ただし、これらの関数は派生値をブロックしないので注意が必要です。

使用例:
APIトークンをクライアントに漏洩させたくない場合。

// data.ts
import { experimental_taintObjectReference, experimental_taintUniqueValue } from 'react';

export async function getUserData(id) {
  const data = /* APIトークンを含むユーザーデータ */;
  experimental_taintObjectReference('Do not pass user data', data);
  experimental_taintUniqueValue('Do not pass tokens', data, data.token);
  return data;
}

experimental_taintUniqueValueは、文字列値そのものをクライアントに渡すことをブロックする機能です。たとえば、APIトークンやセッションIDなどの機密文字列をクライアントに漏洩させたくない場合に使えます。

import { experimental_taintUniqueValue } from 'react';

const apiToken = 'supersecrettoken123';
experimental_taintUniqueValue('No tokens to client', apiToken); 

// APIトークンをクライアントに渡そうとするとエラー
return <ClientComponent token={apiToken} />

ただし、この関数が機能するのは文字列値そのものに対してだけです。オブジェクトのプロパティから文字列を抽出した場合や、文字列を加工した派生値に対してはブロックされません。

例: 派生値はブロックされない

const userData = { name: 'Alice', token: 'supersecret' };
experimental_taintUniqueValue('No tokens', userData, userData.token);

// tokenプロパティから抽出した値は渡せてしまう
const { token } = userData;
return <ClientComponent apiToken={token} />

// 文字列の一部を抽出した値も渡せてしまう 
return <ClientComponent prefix={token.slice(0, 5)}> 

なので、この関数を使う場合は、派生値への対処も意識する必要があります。トークンなどの重要な値は、あくまでもサーバー側でのみ処理し、クライアントには絶対に渡さない実装が理想的です。

データアクセスレイヤーの活用

これらのチェックを行うよりも、最初からデータアクセスレイヤーを使ってサーバーコンポーネントにデータが入り込むことを回避する方が safer です。

環境変数の取り扱い

デフォルトでは環境変数はサーバー側でのみ使用できますが、NEXT_PUBLIC_プレフィックスが付いた変数はクライアントでも利用できます。これにより、クライアントに公開したい構成を明示的に指定できます。

MAAAAAAAAAAAMAAAAAAAAAAA
  1. server-only モジュール
  2. React サーバー コンポーネント プロトコル
  3. experimental_taintObjectReferenceexperimental_taintUniqueValue
  4. データ アクセス レイヤー
  5. 環境変数

各方法について、具体的なコード例と使用場面を推定して説明します。

1. server-only モジュール

server-only モジュールを使用すると、そのモジュール内のコードがサーバー側でのみ実行され、クライアント側では実行されないようにすることができます。これは、次のような機密情報や内部ロジックを含むコードを記述する場合に役立ちます。

  • API キー
  • 認証トークン
  • データベース接続情報
  • ビジネスロジック

コード例:

// server-only.js
import 'server-only';

export function getSecretData() {
  // サーバー側でのみ実行されるコード
  console.log('Secret data:', process.env.API_KEY);
}

使用場面:

  • API キーを使用して外部サービスにアクセスする
  • 認証トークンを使用してユーザーを認証する
  • データベースから機密情報を読み込む
  • ビジネスロジックに基づいてデータを処理する

2. React サーバー コンポーネント プロトコル

React サーバー コンポーネント プロトコルを使用すると、サーバーコンポーネントからクライアントコンポーネントにデータを安全に渡すことができます。このプロトコルは JSON のスーパーセットをサポートしており、カスタムクラスの転送はサポートされていません。

コード例:

// app/data.js
export async function getUserData(id) {
  const data = await fetch(`/api/users/${id}`);
  return data.json();
}

// app/pages/user.tsx
import { getUserData } from '../data';

export async function getStaticProps({ params }) {
  const userData = await getUserData(params.id);
  return {
    props: {
      user: userData,
    },
  };
}

export default function User({ user }) {
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

使用場面:

  • サーバー側で処理されたデータをクライアント側で表示する
  • クライアント側でフォームデータを送信し、サーバー側で処理する
  • サーバー側でキャッシュされたデータをクライアント側で利用する

3. experimental_taintObjectReferenceexperimental_taintUniqueValue

Next.js 14 では、実験的な experimental_taintObjectReferenceexperimental_taintUniqueValue API を使用して、特定のオブジェクトや値をクライアントに渡すことを禁止することができます。

コード例:

// app/data.js
import { experimental_taintObjectReference, experimental_taintUniqueValue } from 'react';

export async function getUserData(id) {
  const data = await fetch(`/api/users/${id}`);
  experimental_taintObjectReference('Do not pass user data to the client', data);
  experimental_taintUniqueValue('Do not pass tokens to the client', data.token);
  return data;
}

使用場面:

  • 特定のオブジェクトや値が誤ってクライアントに公開されるのを防ぐ
  • 機密情報を含むオブジェクトや値を安全に処理する
  • 認証トークンなどの重要な値を保護する

4. データ アクセス レイヤー

データ アクセス レイヤーを使用すると、最初からサーバーコンポーネントにデータが入り込むことを回避することができます。これにより、汚染チェックによる追加の保護層が提供されます。

コード例:

// app/data-access.js
export async function getUserData(id) {
  const data = await prisma.user.findUnique({
    where: {
      id,
    },
  });
  return data
MAAAAAAAAAAAMAAAAAAAAAAA

現代のWeb開発では、クライアントとサーバーの間でコードが共有されることが一般的ですが、機密性の高い情報やサーバー専用のロジックはサーバー上でのみ処理すべきです。これを強化するために、特定のコードがクライアントに送信されないようにする方法がいくつかあります。

サーバー専用コードの識別

使用例:
// server-only.js
import 'server-only';

export function sensitiveFunction() {
  // サーバー専用の処理
  return "Sensitive Data";
}

このマークが付けられたモジュールをクライアントがインポートしようとすると、ビルドプロセス中にエラーが発生します。これにより、機密データや内部ビジネスロジックがクライアントに誤って露出するのを防ぎます。

React サーバー コンポーネント プロトコル

Reactのサーバーコンポーネントでは、Propsを介してデータがクライアントコンポーネントに渡される際に自動的にシリアル化されます。このシリアル化はJSONのスーパーセットをサポートしており、クラスオブジェクトの直接的な転送はサポートされていません。

コード例:
// data.js
import { db } from './database';

export async function fetchUserData(userId) {
  const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);
  return user;
}

// UserPage.js
import { fetchUserData } from './data';

export async function UserPage({ params }) {
  const userData = await fetchUserData(params.id);
  return <UserProfile data={userData} />;
}

React Taint APIの使用

Next.js 14 で導入される予定のReact Taint APIは、特定のデータがクライアントに直接渡されないようにマーキングする機能を提供します。これにより、データが意図せずクライアントに露出するのを防ぐことができます。

コード例:
// next.config.js
module.exports = {
  experimental: {
    taint: true
  }
};

// data.js
import { experimental_taintObjectReference } from 'react';

export async function getUserData(userId) {
  const data = await fetchUserData(userId);
  experimental_taintObjectReference('Do not pass user data to the client', data);
  return data;
}

// UserPage.js
import { getUserData } from './data';

export async function UserPage({ searchParams }) {
  const userData = await getUserData(searchParams.id);
  return <ClientComponent userData={userData} />; // これはエラーを引き起こします
}

このAPIは、データを直接渡すことのほか、派生値や加工データの漏洩も防ぐために役立ちます。また、特定の値(例えばトークンなど)に対してもブロックする機能が提供されています。

環境変数の取り扱い

Next.jsでは、サーバー上でのみ使用すべき環境変数は通常の環境変数として保持し、クライアントに公開すべき設定はNEXT_PUBLIC_の接頭辞をつけることで管理されます。これにより、クライアントで安全に使用できる構成のみが露出され、その他の情報は保護されます。

使用例:
// server.js


console.log(process.env.SECRET_API_KEY); // サーバー専用

// config.js
console.log(process.env.NEXT_PUBLIC_API_URL); // クライアントと共有

まとめ

サーバー専用のコードを適切に管理することは、アプリケーションのセキュリティを保つ上で重要です。適切なツールとプラクティスを利用することで、機密データの保護と効率的な開発が可能になります。