jQueryからNext.jsへ:モダンフロントエンドで挑んだ「S3ファイルダウンロード機能」実装の記録
これまでJavaScriptやjQueryを中心にフロントエンドを触ってきた方が、最新のモダン開発(Next.jsなど)に触れると、その概念の多さに驚くかもしれません。私も先日、約3日間という短期間でフロントエンド開発のキャッチアップを行い、最終的に「メールのURLからS3のCSVをダウンロードする」という機能を実装しました。
今回は、jQuery経験1年程度のエンジニアが、モダンフロントエンドの大枠を把握するために必要な基礎知識を整理してお伝えします。
1. アプリケーションの土台:UIと構造
まずは、ユーザーの目に触れる部分と、アプリの骨組みを作る技術です。
Next.js 16 (App Router)
Webアプリケーションの「全体設計図」です。
jQuery時代はHTMLファイルを1枚ずつ作っていましたが、Next.jsでは app/ フォルダの中にファイルを作るだけで、自動的にページ(URL)として認識されます。
-
ファイルシステムベース:
app/dashboard/page.tsxというファイルを作れば、自動的に/dashboardというURLでアクセス可能になります。
Server Components と Client Components
モダン開発で最も重要な概念の一つです。jQueryでは「すべてブラウザで動かす」のが当たり前でしたが、Reactでは 「どこで動かすか」 を使い分けます。
- Server Components: サーバー側でHTMLを作って送る。セキュリティに強く、読み込みが速い。
-
Client Components (
'use client'): ブラウザで動かす。ボタンのクリック(onClick)や画面の書き換え(useEffect)が必要な場所で使います。
Tailwind CSS & shadcn/ui
CSSを別ファイルに書くのではなく、HTML(JSX)のクラス名としてデザインを直接書き込みます(ユーティリティクラス)。
-
Tailwind CSS:
flex,bg-white,w-fullなどのクラスを組み合わせてレイアウトを作ります。 - shadcn/ui: ゼロからボタンやカードを作るのではなく、洗練されたコンポーネントの「ソースコード」を自分のプロジェクトにコピーして使う手法です。
2. ロジックとデータ連携:安全にデータを扱う
Server Actions ('use server')
ブラウザから「サーバー側の関数」を直接呼び出す仕組みです。
jQueryの $.ajax でAPIを叩く感覚に似ていますが、URLを意識せずに型安全に関数を実行できるのが強みです。
AWS SDK v3 & 署名付きURL
今回はS3からファイルを安全にダウンロードするために 「署名付きURL (Presigned URL)」 を使用しました。
署名付きURLとは?
本来は非公開のファイルに対し、「このURLを知っている人だけ、5分間だけアクセスして良いですよ」という特別な期限付き許可証を発行する仕組みです。
3. 実践:メールリンクからのCSVダウンロード機能
今回のメインイベントです。 「メールのURLをクリックすると、画面には『成功』と表示したまま、裏側でCSVファイルをダウンロードさせる」 という機能をどう実現したか解説します。
実装の要件と課題
ユーザーがメール内のURLを踏んだ際、システムは権限チェックを行い、S3の「署名付きURL(一時的な許可証)」を発行します。
ここで課題となるのが、 「現在の画面を維持したままダウンロードさせる」 点です。普通にS3のURLへリダイレクトしてしまうと、ブラウザのURLバーがS3のものに変わり、アプリの「成功画面」が消えてしまいます。
検討した2つのアプローチと選定理由
検討の結果、今回は 「B. 隠しアンカータグ方式」 を採用しました。
| 比較項目 | A. 別タブ (window.open) 方式 |
B. 隠しアンカータグ方式 【採用】 |
|---|---|---|
| 仕組み | JavaScriptで新しいタブを開き、S3 URLを読み込ませる。 | 裏側で一時的な <a> タグを作り、JSでクリックさせる。 |
| ユーザー体験 | 一瞬白い画面がチカッと表示され、UXがスマートではない。 | 画面遷移が発生せず、現在の画面を保ったままDLが始まる。 |
| リスク | ポップアップブロックに遮断される可能性が高い。 | 同一ウィンドウ内の操作のため、ブロックされにくい。 |
| 選定理由 | ブラウザ環境(モバイル等)によって挙動が不安定。 | 「成功画面を見せ続ける」という要件に最適。 |
実装のコードイメージ
jQueryで例えるなら $('body').append('<a>...</a>').click().remove() を行っているようなイメージです。これをReactの useEffect 内で行います。
// app/download/page.tsx ('use client')
import { useEffect, useState } from 'react';
export default function DownloadPage({ searchParams }: { searchParams: { id: string } }) {
const [status, setStatus] = useState('loading');
useEffect(() => {
const handleDownload = async () => {
try {
// 1. Server Actionsを呼び出してS3の署名付きURLを取得
const { url } = await generateDownloadUrlAction(searchParams.id);
if (!url) throw new Error("URL生成失敗");
// 2. 【ポイント】隠しアンカータグを生成してクリック
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'data.csv');
link.style.display = 'none'; // 画面には出さない
document.body.appendChild(link);
link.click(); // プログラムからクリックを実行!
// 3. 後片付け
document.body.removeChild(link);
setStatus('success');
} catch (e) {
setStatus('error');
}
};
handleDownload();
}, [searchParams.id]);
if (status === 'loading') return <p>準備中...</p>;
return (
<div className="p-4 bg-green-50 border border-green-200 rounded">
<h1 className="font-bold">ダウンロード成功!</h1>
<p>ファイルが保存されました。ブラウザの履歴をご確認ください。</p>
</div>
);
}
まとめ:jQueryとモダンフロントエンドの比較
今回の開発を通じて感じた、jQueryとの決定的な違いをまとめました。
| 項目 | jQuery (これまでの開発) | Next.js / React (モダン開発) |
|---|---|---|
| 画面の書き換え |
$('#id').text('成功') のようにDOMを直接操作する。 |
setStatus('success') のように「状態(State)」を変えると、UIが自動でついてくる。 |
| ファイル管理 | HTML、CSS、JSをバラバラに管理する。 | コンポーネント単位で、見た目もロジックも一つにまとめる。 |
| サーバー連携 |
$.ajax でエンドポイントを叩き、JSONをもらってから加工する。 |
Server Actions で、サーバー側の関数を直接呼ぶ感覚でデータをやり取りする。 |
| スタイリング | CSSファイルに独自のクラス名を作って定義する。 | Tailwind CSS で、用意されたルールを組み合わせてその場でデザインする。 |
jQueryの経験は、ブラウザの挙動(DOM操作やイベント)を理解する上で非常に強力な武器になります。そこに「コンポーネント」や「サーバー/クライアントの役割分担」という概念を加えることで、より堅牢で高速なアプリケーションが作れるようになると思います。