見積書や申込書、契約書のPDFが共有フォルダやメールに散らばり、「最新版はどれ?」「誰が承認した?」と探し回る。社内でよくあるこの状況を、小さな業務システムで解決していく連載を始めます。題材は「PDF管理システム」、使うのは Nuxt(=Vue.js をベースに画面とサーバー処理を1つのプロジェクトで作れるフレームワーク)です。
「PDFの管理システムを作りたいけれど、アップロード・検索・編集・帳票作成まで、どこから手を付ければいいのか分からない…」
結論から言うと、PDF管理システムは「保存する」「探す」「見る」「直す」「作る」「守る」の6つに分けると、1つずつ小さく作れます。この連載では、それぞれを1回1機能で実装し、毎回「動くコード」と「動作確認の方法」をセットで紹介します。第0回の今回は、連載の全体像と、Nuxt 4 で作るプロジェクトの土台(共通レイアウト・トップページ・設定値の置き場)を用意します。
PDF管理システムをNuxtで作る連載の全体像
最終的に完成させるのは、次のことができる社内向けの業務システムです。
- PDFのアップロードと保存(サイズ・形式のチェック付き)
- 一覧・検索(ファイル名・タグ・登録者・日付で絞り込み、ページ送り)
- ブラウザ上でのプレビュー(日本語のPDFも文字化けしない)
- 編集:結合・分割、テキスト注釈、承認印などのスタンプ、署名画像の貼り付け、PDFフォームへの入力
- 生成:見積書・請求書をデータから日本語PDFとして作成
- ログインとロール別の権限(閲覧のみ・編集可・管理者)、権限チェック付きのダウンロード
- 保存先を開発時のパソコン内から本番のクラウドストレージ(Amazon S3)へ切り替え
全体の構成:画面・サーバー・保存先の3層
構成は大きく3つに分かれます。
| 層 | 役割 | 主な技術 |
|---|---|---|
| 画面(ブラウザ) | 一覧・アップロード・プレビュー・編集操作 | Nuxt 4(Vue 3)、pdfjs-dist |
| サーバー | 検証・権限チェック・PDFの加工と生成 | Nuxt に内蔵のサーバー機能(Nitro)、pdf-lib |
| 保存先 | ファイル情報(名前・タグ・登録者)とPDF本体 | SQLite(データベース)、ローカルディスク → S3 |
設計の方針は次の3つです。
- PDF本体とファイル情報を分ける:検索はデータベース、PDFそのものはストレージに置きます。保存するときのファイル名はランダムなID(UUID)にし、元のファイル名はデータベースにだけ持ちます。
- 編集は常に「新しい版」を作る:元のファイルは上書きせず、誤操作からの復元や「誰がいつ直したか」の確認ができるようにします。
- 表示はブラウザ、変更はサーバー:ブラウザではプレビューと位置の指定だけを行い、PDFを実際に書き換える処理は、サーバーで権限を確認してから実行します。
連載の目次(予定)
各回のタイトルは予定です。回を追うごとに、前回までのコードに差分を積み上げていきます。
| 回 | タイトル(予定) |
|---|---|
| 第0回 | 全体像とNuxt 4プロジェクトの土台づくり(この記事) |
| 第1回 | PDFをアップロードして安全に保存する |
| 第2回 | ファイル情報をDBに持ち、一覧を表示する |
| 第3回 | 検索・絞り込み・ページングをつける |
| 第4回 | ログイン機能を入れる |
| 第5回 | 権限管理と「認可つきダウンロード」 |
| 第6回 | ブラウザでPDFをプレビューする |
| 第7回 | PDFの結合と分割 |
| 第8回 | 注釈・スタンプ・署名画像を貼る |
| 第9回 | PDFフォーム(AcroForm)に入力する |
| 第10回 | 見積書・請求書をデータから生成する |
| 第11回 | 本番運用へ:S3への切り替えとデプロイ |
使う主なライブラリと選んだ理由
| 用途 | ライブラリ | 選んだ理由 |
|---|---|---|
| 画面とAPI | Nuxt 4 | 画面(Vue)とAPI(サーバー処理)を1つのプロジェクトで持てる |
| PDFの表示 | pdfjs-dist(Mozilla PDF.js) | ページを自前で描画でき、クリック位置の取得や注釈の重ね表示ができる |
| PDFの編集・生成 | pdf-lib + @pdf-lib/fontkit | 結合・分割・画像やテキストの書き込み・フォーム入力・新規作成を1つのライブラリで書ける |
| ログイン | nuxt-auth-utils | Nuxt 向けの認証モジュール。暗号化されたCookieでログイン状態を保持できる |
| データベース | SQLite + Drizzle ORM | 追加のDBサーバーが不要。テーブル定義を TypeScript で型安全に書ける |
| 入力チェック | zod | 検索条件や送信内容を「形のルール」で検証できる |
pdf-lib については、npm 上の最新版の公開が2021年で、その後の更新が止まっている点がリスクです。また、パスワード付き(暗号化された)PDFは扱えません。連載では学習コストの低さを優先して pdf-lib で進め、終盤で後継のフォーク版への切り替えについても触れる予定です。
Nuxt 4 プロジェクトを作成する
ここからは実装です。作業に使った環境は Node.js 24、npm 11 です。Nuxt 4 の公式ドキュメントでは、Node.js 22 以上(アクティブなLTS版を推奨)が前提とされています。
📰 出典:Nuxt 公式ドキュメント Installation
プロジェクトは公式の作成コマンドで作ります。
npm create nuxt@latest pdf-kanri
cd pdf-kanri
npm run dev
http://localhost:3000 を開いて Nuxt の初期画面が出れば準備完了です。この連載のサンプルでは、作成後に次の2点を変えています。
- パッケージのバージョンを固定する(
package.jsonの^を外す)。連載の途中で勝手にバージョンが上がり、記事どおりに動かなくなるのを防ぐためです。 - 型チェック用に
typescriptとvue-tscを開発用の依存として追加する(npm run typecheckで使います)。
Nuxt 4 のディレクトリ構成:ソースは app/ に置く
Nuxt 4 では、画面側のソースコードは app/ ディレクトリに置くのが既定です。サーバー側の処理は server/(次回以降で作ります)に置きます。第0回の時点のファイル構成は次のとおりです。
pdf-kanri/
├─ app/
│ ├─ app.vue … 画面全体の入口
│ ├─ layouts/default.vue … ヘッダー・フッターの共通レイアウト
│ └─ pages/index.vue … トップページ(/)
├─ public/ … 公開してよい静的ファイルだけを置く
├─ nuxt.config.ts … Nuxt の設定
├─ .env.example … 環境変数のキー名だけを書いた見本
└─ .gitignore
pages/ に置いたファイルは、そのままURLになります(pages/index.vue が /、次回作る pages/files/upload.vue が /files/upload)。ルーティング(=URLと画面の対応付け)の設定を別に書かなくてよいのが Nuxt の特徴です。
共通レイアウトとトップページを作る
app/app.vue:レイアウトを有効にする
app/app.vue はすべての画面の入口です。<NuxtLayout> で囲むと、レイアウト機能が有効になります。
app/app.vue
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
app/layouts/default.vue:ヘッダーとフッター
レイアウトを指定しないページには app/layouts/default.vue が使われます。ページの中身は <slot /> の位置に差し込まれます。
app/layouts/default.vue(スタイルは省略)
<script setup lang="ts">
const config = useRuntimeConfig()
</script>
<template>
<div class="layout">
<header class="layout__header">
<NuxtLink to="/" class="layout__brand">{{ config.public.appName }}</NuxtLink>
<nav class="layout__nav">
<NuxtLink to="/">ホーム</NuxtLink>
</nav>
</header>
<main class="layout__main">
<slot />
</main>
<footer class="layout__footer">
<small>Nuxt.jsで作るPDF管理・編集・作成の業務システム(サンプル)</small>
</footer>
</div>
</template>
ヘッダーのシステム名は、後で説明する設定値(runtimeConfig.public.appName)から読み込んでいます。ナビゲーションのリンクは、機能を追加するたびに増やしていきます。
app/pages/index.vue:トップページ
トップページには、これから作る機能の一覧を仮置きしておきます。以降の回では、ここを各機能への入口にしていきます。
app/pages/index.vue
<script setup lang="ts">
useHead({ title: 'ホーム | PDF管理システム' })
// 第1回以降で実装していく機能(プレースホルダー)
const roadmap: string[] = [
'PDFのアップロードと保存',
'一覧・検索',
'ブラウザでのプレビュー',
'注釈・結合・分割・フォーム入力・スタンプ',
'見積書・請求書のPDF生成',
'ログインと権限管理',
]
</script>
<template>
<section>
<h1>PDF管理システム</h1>
<p>第0回:プロジェクトの土台(レイアウトとトップページ)です。</p>
<h2>これから作る機能</h2>
<ul>
<li v-for="item in roadmap" :key="item">{{ item }}</li>
</ul>
</section>
</template>
連載のコードはすべて <script setup lang="ts">(Vue の Composition API + TypeScript)で書きます。
設定値と秘密情報の置き場を決める
業務システムでは、保存先のフォルダ、アップロードの上限サイズ、ログイン用の鍵など「環境によって変わる値」や「外に漏らしてはいけない値」がたくさん出てきます。これらを最初にどこへ置くか決めておくと、後の回で迷いません。
nuxt.config.ts の runtimeConfig
Nuxt では runtimeConfig に設定値を定義します。直下に書いた値はサーバー側だけで読め、public の中に書いた値はブラウザにも渡されます。
nuxt.config.ts
export default defineNuxtConfig({
compatibilityDate: '2026-09-27',
devtools: { enabled: true },
typescript: {
strict: true,
},
app: {
head: {
htmlAttrs: { lang: 'ja' },
title: 'PDF管理システム',
},
},
// 秘密情報はここに直書きせず、.env(NUXT_ 接頭辞の環境変数)で上書きする
runtimeConfig: {
uploadDir: './uploads',
public: {
appName: 'PDF管理システム',
},
},
})
typescript.strict: true で型チェックを厳しめにしています。htmlAttrs.lang を ja にしておくと、ブラウザや読み上げソフトが日本語のページとして扱えます。
.env と .env.example の使い分け
runtimeConfig の値は、NUXT_ で始まる環境変数で上書きできます。たとえば uploadDir は NUXT_UPLOAD_DIR で変えられます。開発中は .env ファイルに書いておけば Nuxt が読み込みます。
📰 出典:Nuxt 公式ドキュメント Runtime Config
.env.example
# 第0回時点では未使用。以降の回で runtimeConfig から参照する。
# NUXT_UPLOAD_DIR=./uploads
# NUXT_SESSION_PASSWORD=change-me-at-least-32-characters-long
ルールは次のとおりです。
.envには実際の値を書き、Git にはコミットしない(.gitignoreに登録済み).env.exampleにはキー名とダミー値だけを書き、コミットするruntimeConfig.publicには、ブラウザに見えてもよい値だけを置く(鍵やパスワードは絶対に入れない)
注意点として、.env を読むのは開発時とビルド時の Nuxt のコマンドです。ビルドしたサーバーを本番で起動するときには .env は読まれないため、サーバーの環境変数として渡す必要があります。
.gitignore:アップロードしたPDFもコミットしない
.gitignore には、ビルド結果や node_modules に加えて、.env と、次回から使うアップロード保存先の uploads を入れています。社内の書類が誤ってリポジトリに入らないようにするためです。
動作確認の方法
次の手順で確認できます(筆者の環境でも、開発サーバーと本番ビルドの両方でトップページが表示されることを確認しました)。
npm install
cp .env.example .env
npm run dev # http://localhost:3000 を開く
npm run typecheck # 型チェック(エラーが出ないこと)
npm run build
node .output/server/index.mjs # ビルド結果で起動し、同じ画面が出ること
- ヘッダーに「PDF管理システム」、フッターに連載名が出る
- トップページに「これから作る機能」の一覧が出る
npm run buildの後、node .output/server/index.mjsでも同じ画面が表示される
つまずきやすい点
- 型チェックのコマンドが動かない:
npm run typecheck(nuxt typecheck)にはtypescriptとvue-tscが必要です。筆者の環境では、執筆時点で最新の TypeScript 7 を入れるとvue-tscの起動でエラーになったため、TypeScript 6 に固定しています。型チェックが動かないときは、両者の組み合わせを確認してください。 - ファイルを app/ の外に置いてしまう:Nuxt 3 の頃の記事ではプロジェクト直下に
pages/を置く例が多く見られます。Nuxt 4 ではapp/pages/です。 - public/ に業務ファイルを置かない:
public/の中身は誰でもURLで取得できます。PDFの保存先は必ず別の場所にし、ダウンロードはサーバーで権限を確認してから返します(第5回で実装します)。
発注者向けメモ:最初に決めておくと手戻りが減る3つのこと
PDF管理システムを開発会社に依頼する場合、画面を作り始める前に次の3点を決めておくと、後からの作り直しが減ります。
- ファイルの上限サイズ:スキャンした書類は1ファイル数十MBになることもあります。上限によって、保存先の容量やアップロードの作り(分割送信が必要か)が変わります。
- 保存期間と削除のルール:法令や社内規程で保存期間が決まっている書類があるか、削除した書類を復元できる必要があるか。
- 誰が何をできるか:閲覧だけの人、編集できる人、削除できる人の区分。部署ごとに見える範囲を分けるかどうかで、作る量が大きく変わります。
打ち合わせでは、次のように聞いてみてください。
- 「扱うPDFの最大サイズと、1か月あたりの件数の想定を伝えれば、保存先の構成を提案していただけますか?」
- 「パスワード付きのPDFや、スキャンしただけのPDFも扱えますか?扱えない場合、どう運用すればよいですか?」
- 「使うライブラリの更新状況(メンテナンスが続いているか)と、止まった場合の代替案を教えてください」
- 「設定値や鍵はどこで管理し、本番環境ではどう渡す予定ですか?」
まとめと次回予告
第0回では、PDF管理システムの全体像と連載の目次を示し、Nuxt 4 プロジェクトの土台を作りました。
- PDF管理を「保存・検索・表示・編集・生成・権限」に分け、1回1機能で作る
- Nuxt 4 では画面のソースを
app/に置き、pages/のファイルがそのままURLになる - 共通レイアウトは
app/layouts/default.vueと<NuxtLayout>で作る - 設定値は
runtimeConfig、秘密情報は.env(コミットしない)、見本は.env.example
次回は「PDFをアップロードして安全に保存する」です。アップロードされたファイルが本当にPDFかをサーバー側で確かめ、ランダムなファイル名で保存するところまでを実装します。
この連載の記事一覧
この記事は連載「Nuxtで作るPDF管理システム」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。
- 【Nuxtで作るPDF管理システム 第0回】全体像とNuxt 4プロジェクトの土台づくり(この記事)
- 【Nuxtで作るPDF管理システム 第1回】PDFをアップロードして安全に保存する
- 【Nuxtで作るPDF管理システム 第2回】ファイル情報をDBに持ち、一覧を表示する
- 【Nuxtで作るPDF管理システム 第3回】検索・絞り込み・ページングをつける
- 【Nuxtで作るPDF管理システム 第4回】ログイン機能を入れる
- 【Nuxtで作るPDF管理システム 第5回】権限管理と「認可つきダウンロード」
- 【Nuxtで作るPDF管理システム 第6回】ブラウザでPDFをプレビューする
- 【Nuxtで作るPDF管理システム 第7回】PDFの結合と分割
- 【Nuxtで作るPDF管理システム 第8回】注釈・スタンプ・署名画像を貼る
- 【Nuxtで作るPDF管理システム 第9回】PDFフォーム(AcroForm)に入力する
- 【Nuxtで作るPDF管理システム 第10回】見積書・請求書をデータから生成する
- 【Nuxtで作るPDF管理システム 第11回】本番運用へ:S3への切り替えとデプロイ










コメント
コメント一覧 (2件)
[…] 前回の第0回:全体像とNuxt 4プロジェクトの土台づくりでは、連載の全体像と、共通レイアウト・設定値の置き場を用意しました。今回はそこに、Nuxt のサーバー機能を使ったPDFのアップロードAPIと画面を追加します。 […]
[…] 全体像とNuxt 4プロジェクトの土台づくり […]