15
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vercel初心者が無料プランで自動デプロイ・ストレージ・APIまで一通り触ってみた

Last updated at Posted at 2025-12-21

はじめに

いよいよアドベントカレンダーも21日目ですね!さて、私はこの2025年に新卒で入社し、プロジェクト参画にあたって初めてフロントエンド技術に触れた初心者でございます。フロントエンドを触っていると「自分で何か作りたいなぁ」と思うわけです。そんな時の強い味方がVercelでございます。今回は、Vercelの無料枠の範囲内で簡単なアプリを作成し、私自身の備忘録も兼ねて主要な機能をまとめていきたいと思います!

想定する読者の方々

本記事は「Vercel触ったことないけど、これから始めてみたい」という方へ読んでいただきたい記事になっております。網羅的に機能を紹介するというよりは、Webアプリを構築するにあたり高い確率で使用するであろうものを絞ってお伝えしていきます。

Vercelとはなんぞ

Vercelを一言で表せば、
「フロントエンド開発者のためのクラウド開発基盤」
です。

まずは「クラウド開発基盤」という言葉について。Vercelは単なるホスティングサービスではなく、開発・デプロイ・メトリクスにまつわるクラウドネイティブツール群一式をまとめ上げた開発基盤を提供しています。Webアプリケーションの構築・運用をトータルで支えていただける大変ありがたい存在なわけです。

これだけでも十分ありがたいのですが、わざわざ先頭に「フロントエンド開発者のため」とつけた理由は、Vercelが提唱する「フロントエンドクラウド」という概念を踏まえてのことです。

彼らは近年のフロントエンド開発者を取り巻く多くの課題について憂慮しています。ニーズの多様さ(UXの向上に加えて高速であること等)、急速に進化する技術やベストプラクティスが複雑に絡まる環境、厳格なセキュリティとコンプライアンスetc...
とにかく考えることが多いのです。これを解決するために登場したのがフロントエンドクラウドであり、Vercelの存在意義そのものでもあります。その主な要素は以下の3つにまとめられます。

1. グローバルインフラストラクチャとキャッシュ
CDNなどを通じて、Webアプリケーションの低遅延・高可用性を実現している。

2. 統一された開発者ツールとワークフロー
Vercelは主要なフロントエンドのフレームワーク・ライブラリを広くカバーしている。これによって開発者は統一されたワークフローの下で構築→テスト→デプロイをシームレスに行うことができる。

3. アプリケーションの可観測性(Observability)と監視(Monitoring)
開発者はVercelが持つ高度な分析ツールによって、アプリケーションのパフォーマンスを効果的に監視し、問題へ迅速に対応できるようになる。

このようにフロントエンド開発者にとって心強い仕組みが整っているのがVercelなのです。気軽にWebアプリケーションを作成したいフロントエンド開発者を惹きつけてやまない理由はここにあります。

Vercelの料金体系

image.png

Vercelには主に3つの料金プランが存在します。一番左のHobbyプランは無料でVercelを使えるプランですが、使えるリソースに限りがあります。

Delivery Network

Resource Limit / Description
Edge Requests 1 Million / month
Fast Data Transfer 100 GB / month

Web Application Firewall

Resource Limit / Description
Custom Firewall Rules 3つまで
IP Blocking 3つまで
Rate Limiting 1 Million / month

Storage

Resource Limit / Description
Blob Storage 1 GB / month
Edge Config 100,000 Reads & 100 writes / month
Neon 0.5GB / project (Free plan)
Redis(KV) Storage 30 MB, single DB (Free plan)

Compute

Resource Limit / Description
Vercel Functions(呼び出し回数) 1 Million / month

Proにアップグレードするとこれらの上限は原則なくなり、 月額固定料金($20)+従量課金(一定量を超えた後) というハイブリッドな料金体系になっております。またこれらのリソース上限に加えて、商用利用が不可となっています。個人向けのPro、企業向けのEnterpriseプランであれば商用利用が認められています。

Vercelの機能

ここで具体的な機能をいきなり羅列していくのはあまりにも酷なので、今回は練習で実際にアプリを作り、そこで用いた機能を中心にご紹介していくことにします。作成したのは「アルバムアプリ」です。
image.png

こちらは私が毎月貢いでいるOpenAIよりCodex先生がお書きになり、弟子の私が若干修正したものです。今回は「Vercelを理解すること」に重きを置いていますので、その点ご容赦いただけますと幸いです(にしても、AIでコーディングされたUIってなんとなく分かってしまうのなぜなんでしょうね?)。

今回私が用いたVercelの機能は以下の5つです。

No. 機能 概要 今回の用途
1 Neon for Vercel PostgreSQL データベース 各写真のメタデータを保存
2 Vercel Blob オブジェクトストレージ 画像ファイル自体を保存
3 Vercel Functions サーバレス関数 フロントからのHTTPリクエストを捌くAPI
4 GitHubからの自動デプロイ プッシュすると自動でビルド&デプロイ 特定のリポジトリに紐づけて、プッシュと同時に自動デプロイ
5 Logs アプリのログを一元管理 ビルド時やランタイムのログを監視

では実際の画面とともに各機能の特徴を見ていきましょう!

Neon for Vercel

こちらはVercel上のプロジェクトと容易に統合できるよう設計された、サーバレスのPostgreSQLサービスです。

Neonについて
NeonはPostgreSQLをクラウドサービスとして再構築し、よりスケーラブルで信頼性の高いアプリケーション構築に貢献することをミッションとしています。

Vercelをよくご存知でおられる同志の皆さまは「あれ、Vercel PostgreSQLってあったやん」と思われていることでしょう。実はこちら、2025年の第一四半期までにNeonへ完全に移行されました(参考:https://neon.com/docs/guides/vercel-postgres-transition-guide )。比較的最近の出来事なので、生成AIはこれを知らずに応答してくることがあります。

では実際に設定してみましょう。まずはDashboardの上部メニューバーから”Storage”を選択します。

homeStorage.png

そして”Create Database”をクリックします。

Screenshot 2025-12-17 at 6.10.22.png

そうするとStorageの選択画面が出てきますので、今回は”Neon”をクリックします。

Screenshot 2025-12-17 at 6.10.47 copy.png

ストレージの名前を入力して、”Create”をクリックすれば作成完了です。

Screenshot 2025-12-17 at 6.23.19.png
Screenshot 2025-12-17 at 6.23.55.png

そうすると、接続先のURL等の .envに追記すべき内容が書かれています。こちらを自身の環境の .envにコピペします。

Screenshot 2025-12-17 at 6.24.35.png

Vercel Blob

こちらはVercelに標準搭載されているオブジェクトストレージサービスです。感覚としてはAWSにおけるS3のようなものです。BlobとはBinary Large Objectの略称で、汎用的なバイナリデータの塊です。テキストや画像、動画など様々なデータ形式を扱うことができ、フロントでファイルを扱おうとすると頻繁に出てきます。

こちらも先ほどのNeonと同様にDashboard>"Storage"から設定することができます。実際にやってみましょう。最初の部分は省かせていただきまして、ストレージの名前を設定して”Create”で作成完了です。

Screenshot 2025-12-17 at 6.10.47.png

Screenshot 2025-12-17 at 6.12.53.png

こちらもNeonと同様に、ストレージへのアクセストークンが発行されるので自身の環境にある .envへコピペしましょう。

Screenshot 2025-12-17 at 6.13.19.png

Vercel Functions

こちらはサーバレスなバックエンドの仕組みを提供するものであり、APIエンドポイントの実装やDBアクセスの実現を担います。例えばAPIエンドポイントであれば、プロジェクトのルートに api/ というディレクトリを作成した上でコードを配置すると、Vercelが自動的にエンドポイントをデプロイしてくれます。

APIのディレクトリ構成
今回はフレームワーク非依存で、プロジェクト直下にapi/ディレクトリを置くような構成にしております。しかしながら、Vercelでアプリを構築する際はNext.jsと合わせて利用されることが多く、App Routerを用いることになります。その際、APIは app/api直下へ置くことになります。例えばphotosというAPIを実装したければ、app/api/photos/というディレクトリを作成し、その直下のroute.tsにロジックを記述します。

今回のアプリでは、以下のようなコードを実装しています。

api/photos.ts
import type { VercelRequest, VercelResponse } from '@vercel/node'
import { neon } from '@neondatabase/serverless'
import { put } from '@vercel/blob'
import { randomUUID } from 'crypto'

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '10mb',
    },
  },
}

type PhotoRow = {
  id: string
  title: string
  image_url: string
  created_at: string
}

const databaseUrl = process.env.DATABASE_URL
const sqlClient = databaseUrl ? neon(databaseUrl) : null
let ensured = false
class ApiError extends Error {
  status: number

  constructor(message: string, status = 400) {
    super(message)
    this.status = status
  }
}

export default async function handler(req: VercelRequest, res: VercelResponse) {
  res.setHeader('Access-Control-Allow-Origin', '*') //動作確認を簡素化するために、どのOriginからでもAPI呼び出しができるように設定しています
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type')

  if (req.method === 'OPTIONS') {
    return res.status(200).end()
  }

  try {
    if (req.method === 'GET') {
      const photos = await listPhotos()
      return res.status(200).json({ photos })
    }

    if (req.method === 'POST') {
      const photo = await createPhoto(req)
      return res.status(201).json({ photo })
    }

    res.status(405).json({ error: 'Method not allowed' })
  } catch (error) {
    console.error(error)
    if (error instanceof ApiError) {
      res.status(error.status).json({ error: error.message })
      return
    }

    const message = error instanceof Error ? error.message : 'Unexpected error'
    res.status(500).json({ error: message })
  }
}

async function ensureTable() {
  if (ensured) return
  const sql = requireSql()

  await sql`
    CREATE TABLE IF NOT EXISTS photos (
      id TEXT PRIMARY KEY,
      title TEXT NOT NULL,
      image_url TEXT NOT NULL,
      created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
    )
  `
  ensured = true
}

async function listPhotos() {
  await ensureTable()
  const sql = requireSql()
  const rows = (await sql`
    SELECT id, title, image_url, created_at
    FROM photos
    ORDER BY created_at DESC
  `) as PhotoRow[]
  return rows.map(mapRow)
}

async function createPhoto(req: VercelRequest) {
  if (!process.env.BLOB_READ_WRITE_TOKEN) {
    throw new ApiError('BLOB_READ_WRITE_TOKEN is not configured', 500)
  }

  const payload = (req.body ?? {}) as {
    title?: string
    fileData?: string
    contentType?: string
  }

  if (!payload.fileData) {
    throw new ApiError('fileData is required')
  }

  const { buffer, contentType } = decodeFile(payload.fileData, payload.contentType)
  const id = randomUUID()
  const blob = await put(`album/${id}`, buffer, {
    access: 'public',
    contentType,
  })

  await ensureTable()
  const sql = requireSql()
  const rows = (await sql`
    INSERT INTO photos (id, title, image_url)
    VALUES (${id}, ${payload.title?.trim() || 'Untitled'}, ${blob.url})
    RETURNING id, title, image_url, created_at
  `) as PhotoRow[]
  return mapRow(rows[0] as PhotoRow)
}

function decodeFile(fileData: string, explicitType?: string) {
  const match = fileData.match(/^data:(.*?);base64,(.*)$/)
  const base64 = match ? match[2] : fileData
  const contentType = explicitType || (match ? match[1] : 'application/octet-stream')
  return {
    buffer: Buffer.from(base64, 'base64'),
    contentType: contentType || 'application/octet-stream',
  }
}

function mapRow(row: PhotoRow) {
  return {
    id: row.id,
    title: row.title,
    imageUrl: row.image_url,
    createdAt: row.created_at,
  }
}

function requireSql() {
  if (!sqlClient) throw new ApiError('DATABASE_URL is not configured', 500)
  return sqlClient
}

handlerという関数を見ていただくとわかりやすいのですが、リクエストreqの内容に応じて各種レスポンスresを返すための処理が書かれています。しかもそれがTypeScriptで書かれていますので、そこまで重い処理でなければAPIの実装がフロントエンド言語のみで完結します。

GitHubからの自動デプロイ

ここからはいよいよVercelにアプリをデプロイしていきます。Vercelでは、ビルド→デプロイを自動的に行ってくれます。その方法としては、現在主に3種類が提供されています。

1. GitにPush
GitHubやGitLabと連携して、特定ブランチへのコミットをトリガーとして自動的にビルド・デプロイ
2. Vercel CLI
ローカルでvercelコマンドを用いることで、ローカルからVercelのクラウドインフラストラクチャへデプロイされる
3. Dashboardから
Vercelが用意しているコンソール画面からデプロイすることが可能。

今回は1番目のGitを用いた自動デプロイを行ってみます。Dashboardの”Overview”にある”Add new...”から”Project”を選びます。

Screenshot 2025-12-17 at 6.27.44.png

こちらの画面では、連携されているGitHubアカウントに存在するRepositoriesが表示されます。ここから、今回新規作成するProjectへImportしたいRepositoryを選択します。

Screenshot 2025-12-17 at 6.29.22.png

デフォルトではRepository名がそのままProject名になります。表示されている内容に問題なければ、そのまま”Deploy”をクリックします。

Screenshot 2025-12-17 at 6.29.42.png

うまくいくと正常にデプロイされます。もしビルドに失敗すると真っ赤になりますので、エラーと格闘しながら解消していきましょう。

Screenshot 2025-12-17 at 6.30.40.png

と、ここで忘れてはいけないのがStorageとProjectを接続することです。以下のボタンをクリックして、対応づけるProjectを指定しないと上手く疎通しませんのでご注意を!

Screenshot 2025-12-17 at 6.35.00.png

ではアプリを確認してみましょう。こちらの”Visit”をクリックするとデプロイされたアプリの画面に遷移します。

Screenshot 2025-12-17 at 6.31.06.png

うまく写真をアップロードできるでしょうか...

Screenshot 2025-12-17 at 6.53.27.png

うまくいきました!!

Screenshot 2025-12-17 at 7.01.57.png

超単純なアプリですが、きちんと動くと嬉しいですね。

CI/CDについて
今回のようにVercelのコンソール画面からGitHubとの連携を行うことで、少なくともCDは実現できていることになります。したがって、単純にデプロイまでの自動化を行いたい場合、GitHub ActionsなどのCI/CDツールは必ずしも必要とは限りません。もちろん、LintやFormatting、テストなどを行った上でデプロイさせたい場合にはGitHub Actionsを利用する必要があります。

Logs

無事アプリをデプロイできたら、Logsを確認してビルド・実行時のログを監視して上手く動いているかどうかを確認しましょう。Dashboardの”Logs”をクリックします。

Screenshot 2025-12-17 at 6.31.06 copy.png

お、きちんとログが流れてきていますね!Requestの列を見てみると/api/photosをエンドポイントとしてVercel Functionsが動いていることを示す$f$マークが見えますね。

Screenshot 2025-12-17 at 7.02.32.png

下の真っ赤なログは、私が格闘した跡です。

最後に

いかがでしたでしょうか?今回の記事を執筆するにあたり色々と調査したのですが、想像していたよりもVercelで出来ることが多く、創作意欲が刺激されたように思います(ただ正直、無料枠だと利用できるストレージやコンピューティングリソースはかなり限られてきます)。今後色々と作っていきたいなと感じました。皆さんもぜひ、Vercelで遊んでみてください!
最後までお読みいただき、ありがとうございました!

15
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?