MENU

問い合わせ


    【Nuxtで作るPDF管理システム 第0回】全体像とNuxt 4プロジェクトの土台づくり

    見積書や申込書、契約書の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への切り替えとデプロイ

    使う主なライブラリと選んだ理由

    用途ライブラリ選んだ理由
    画面とAPINuxt 4画面(Vue)とAPI(サーバー処理)を1つのプロジェクトで持てる
    PDFの表示pdfjs-dist(Mozilla PDF.js)ページを自前で描画でき、クリック位置の取得や注釈の重ね表示ができる
    PDFの編集・生成pdf-lib + @pdf-lib/fontkit結合・分割・画像やテキストの書き込み・フォーム入力・新規作成を1つのライブラリで書ける
    ログインnuxt-auth-utilsNuxt 向けの認証モジュール。暗号化された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

    📰 出典:Nuxt 公式ドキュメント app.vue

    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 /> の位置に差し込まれます。

    📰 出典:Nuxt 公式ドキュメント layouts

    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回です。連載のほかの回は次のとおりです(連載の一覧ページ)。

    システム制作・運用・保守のお問い合わせはこちら


      よかったらシェアしてね!
      • URLをコピーしました!
      • URLをコピーしました!

      この記事を書いた人

      株式会社THIRD HERO代表取締役 朝野貴朗
      Webシステム開発を中心に、toC向けサービスサイトの運営、ツール開発などを行ってまいりました。

      コメント

      コメント一覧 (2件)

      目次