ログオフ
開発2026-06-06

Next.js App Router × Supabase:service role key をサーバー専用にして anon key を公開しない設計

ISR・サーバーコンポーネントで Supabase を使うとき、service role key をサーバー側に閉じれば NEXT_PUBLIC_ に anon key を出さずに済む。設計と注意点をまとめた。

Next.js App Router で Supabase を使うとき、anon key を NEXT_PUBLIC_ に入れたくない場合がある。サーバーサイドに閉じた設計でどう解決するかをまとめた。

事象・背景

Next.js のサーバーコンポーネントや ISR で Supabase からデータを取得したい。しかし NEXT_PUBLIC_SUPABASE_ANON_KEY をフロントに出したくない(クライアント側でユーザーが Supabase に直接アクセスしないアーキテクチャ)。

解決策:サーバー専用クライアントを作る

NEXT_PUBLIC_ を使わず、サーバーサイドのみで動く Supabase クライアントを作る。

// lib/supabaseServer.ts
import { createClient } from '@supabase/supabase-js';

export const supabaseServer = createClient(
  process.env.SUPABASE_URL!,          // NEXT_PUBLIC_ なし → サーバー専用
  process.env.SUPABASE_SERVICE_ROLE_KEY!,  // service role key
);
// app/page.tsx(サーバーコンポーネント)
import { supabaseServer } from '@/lib/supabaseServer';

export default async function Page() {
  const { data } = await supabaseServer
    .from('articles')
    .select('*')
    .eq('published', true);

  return <ArticleList articles={data} />;
}

環境変数は SUPABASE_URLSUPABASE_SERVICE_ROLE_KEY のみ。NEXT_PUBLIC_ なしなのでブラウザには一切届かない。

注意点:service role key は RLS を無視する

service role key は Row Level Security(RLS)を完全にバイパスする。anon key では弾かれるデータも全て取得・更新できてしまう。

サーバー側で使う場合でも、クエリに必ず適切なフィルタを入れる。「サーバーにしか届かないから大丈夫」という油断は危ない。

// ❌ RLS バイパス状態で全件取得(意図しないデータ漏れのリスク)
const { data } = await supabaseServer.from('users').select('*');

// ✅ 必要な条件を明示的に絞る
const { data } = await supabaseServer
  .from('articles')
  .select('*')
  .eq('published', true)
  .order('date', { ascending: false });

補足

  • このパターンはデータ取得がサーバー完結する場合(ISR・SSG・サーバーコンポーネント)に向いている。クライアント側でリアルタイム更新や認証フローが必要な場合は anon key + RLS の設計が適切
  • SUPABASE_SERVICE_ROLE_KEY は Vercel の環境変数に登録し、NEXT_PUBLIC_ なしにすることで本番ビルドでも安全に扱える
  • ISR のキャッシュと組み合わせると、DB アクセスをビルド時・再検証時だけに抑えて負荷を下げられる

※ 本記事にはアフィリエイトリンクが含まれます。

開発 一覧へ