【第23回】ブラウザに置いてはいけないものを知る|サーバーサイド(嫁子が教えるTS講座)
今回のゴール
- ブラウザに置いたものは、全部見られるという前提が分かる
- サーバー側でしかできないことが何かを説明できる
- Next.jsの
"use client"が何を分けていたのかが分かる
ここから第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つです。
.env.localは Git に含めない。create-next-appで作ったプロジェクトなら、最初から除外設定に入っていますNEXT_PUBLIC_で始まる名前を付けると、ブラウザに送られます。秘密情報には絶対に付けない
2つ目は事故が起きやすいところです。「読み込めないから」という理由で NEXT_PUBLIC_ を付けて解決してしまうと、その瞬間に秘密ではなくなります。
嫁子: 「動かないから NEXT_PUBLIC_ を付けたら動きました」は、いちばんやってはいけない直し方なの。
AIに「環境変数が読めない」と相談すると、その提案が出てくることがあるよ。動く/動かないの前に、それは外に出していい値かを自分で判断してね。
つまずきポイント
.env.local を変更したら、開発サーバーを再起動してください。起動時に読み込まれるので、書き換えただけでは反映されません。
GitHubに上げる前に、鍵が含まれていないか確認してください。一度公開されたものは、あとから消しても記録に残ります。第3回でやった git status で、何が記録されようとしているかを見る習慣が効いてきます。
今回のまとめ
- ブラウザに送ったコードとデータは、誰でも読める
- APIキー・パスワード・データベースの接続情報は、サーバー側にだけ置く
- 入力チェックは画面側とサーバー側の両方に書く(役割が違う)
- Next.jsは既定がサーバー側。
"use client"を書いたものだけブラウザで動く - 秘密の値は
.env.localに。NEXT_PUBLIC_を付けたらブラウザに送られる
次にやること
次回はAPIを作るです。画面とサーバーをつなぐ窓口を用意して、ブラウザからサーバーの処理を呼べるようにします。
今回の宿題:よく使っているサイトを開いて、開発者ツールのNetworkタブを見てください。通信の中身が全部見えることを確認するのが目的です。自分が作る側になったときの前提が変わります。
嫁子: 見せたくないものは最初からサーバーに置く。それだけ覚えて帰ってね。
後から隠すのは無理だからね。順番が逆にならないように。