【第23回】ブラウザに置いてはいけないものを知る|サーバーサイド(嫁子が教えるTS講座)

今回のゴール

ここから第5部(バックエンドとデータ)です。前回まで(第4部)で画面は作れるようになりました。全体の流れは講座の目次へ。


嫁子: 第4部の最後で「ページを閉じると全部消える」って言ったでしょ。それを直す話に入るんだけど、その前に今日は1回だけ、こわい話をするね。

ブラウザに置いたものは、全部読まれる前提なの。ここを知らずに進むと、事故の中でもいちばん取り返しがつかないやつを踏むよ。


1. ブラウザに送ったものは、全部見える

第4部で作った画面は、すべてブラウザの中で動いていました。ここで大事な事実が1つあります。

ブラウザに送られたコードとデータは、誰でも中身を見られます。

第9回で開いた開発者ツールを思い出してください。あそこでは自分のサイトを見ましたが、同じことが他人のサイトでもできます。読み込まれたJavaScriptのファイルも、通信の中身も、全部そこに表示されます。

つまり、こういうコードを画面側に書くと、書いた内容がそのまま公開されます。

// ✕ これはブラウザに送られる=誰でも読める
const API_KEY = "sk-abcd1234...";

「難読化すれば大丈夫では」と思うかもしれませんが、難読化は読みにくくするだけで、読めなくはできません。動かすためにはブラウザが解釈できる形である必要があるので、人間にも解釈できます。

嫁子: ここ、AIに任せていると特に危ないところなの。

「この機能を追加して」と頼んだら、AIが動くコードを書いてくれる。動くから正しく見えるでしょ。でも、それが画面側なのかサーバー側なのかまでは、こっちが見ないと分からないんだよね。


2. サーバー側にしか置けないもの

では、何をサーバー側に置くのか。判断の基準は単純で、利用者に見せてはいけないものです。

置き場所 具体例
サーバー側だけ APIキー、パスワード、データベースの接続情報、他人のデータ
どちらでもいい 画面の見た目、入力チェック、表示の切り替え

APIキーというのは、外部サービスを使うときの合鍵のようなものです。これが漏れると、他人があなたの名前でそのサービスを使えます。使った分の料金も、あなたに請求されます。

もうひとつ、見落としやすいものがあります。入力チェックだけを画面側でやってはいけません。

// 画面側のチェックは「親切のため」であって、防御にはならない
if (name === "") {
  return;
}

第21回で書いた空欄チェックは、入力する人を助けるためのものです。悪意のある人はブラウザを経由せず、サーバーに直接リクエストを送れます。本当に守りたいなら、同じチェックをサーバー側にも書きます。

嫁子: 二重に書くのは無駄に見えるでしょ。でも役割が違うの。

画面側は「使いやすさ」のため、サーバー側は「守り」のため。画面側のチェックだけで済ませているコードは、鍵のかかっていない家に「入らないでください」と貼り紙をしているのと同じだよ。


3. Next.jsでの「サーバー側」

第20回で "use client" を書きました。あのとき「理由は第5部で」と言ったのが、ここです。

Next.jsでは、何も書かなければサーバー側で動きます。

書き方 どこで動くか できること
既定(何も書かない) サーバー 秘密情報を扱える、データベースに触れる
"use client" を書く ブラウザ useState、クリックの処理

サーバー側で動くものをServer Component、ブラウザ側をClient Componentと呼びます。名前は覚えなくていいので、「既定はサーバー、操作が必要なものだけブラウザ」という関係だけ押さえてください。

サーバー側では、こういう書き方ができます。

// app/page.tsx("use client" を書かない)
export default async function Page() {
  const items = await getItemsFromDatabase();

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

関数に async がついていて、その中でデータを取ってきています。ここで取得したデータのうち、画面に出したものだけがブラウザに送られます。接続情報や取得の手順は送られません。

嫁子: 第8回でやった async が、こんな形で出てくるとは思わなかったでしょ。

画面を返す関数が、そのまま非同期になれるの。useEffect でデータを取りに行かなくていいのは、第20回で「useEffectを疑え」と言った話ともつながってるよ。


4. 秘密情報の置き方

サーバー側で使う秘密の値は、コードに直接書かず環境変数という仕組みに入れます。

プロジェクトの一番上に .env.local というファイルを作り、こう書きます。

DATABASE_URL="file:./dev.db"
SOME_API_KEY="sk-abcd1234..."

コードからはこう読みます。

const key = process.env.SOME_API_KEY;

大事なのは次の2つです。

2つ目は事故が起きやすいところです。「読み込めないから」という理由で NEXT_PUBLIC_ を付けて解決してしまうと、その瞬間に秘密ではなくなります。

嫁子: 「動かないから NEXT_PUBLIC_ を付けたら動きました」は、いちばんやってはいけない直し方なの。

AIに「環境変数が読めない」と相談すると、その提案が出てくることがあるよ。動く/動かないの前に、それは外に出していい値かを自分で判断してね。


つまずきポイント

.env.local を変更したら、開発サーバーを再起動してください。起動時に読み込まれるので、書き換えただけでは反映されません。

GitHubに上げる前に、鍵が含まれていないか確認してください。一度公開されたものは、あとから消しても記録に残ります。第3回でやった git status で、何が記録されようとしているかを見る習慣が効いてきます。


今回のまとめ


次にやること

次回はAPIを作るです。画面とサーバーをつなぐ窓口を用意して、ブラウザからサーバーの処理を呼べるようにします。

今回の宿題:よく使っているサイトを開いて、開発者ツールのNetworkタブを見てください。通信の中身が全部見えることを確認するのが目的です。自分が作る側になったときの前提が変わります。

嫁子: 見せたくないものは最初からサーバーに置く。それだけ覚えて帰ってね。

後から隠すのは無理だからね。順番が逆にならないように。