mybook

テクニカルデモの技法 — 「Wow」を設計する

「よし、全機能を見せよう。ダッシュボードの分析画面から始めて、ユーザー管理、権限設定、API連携、レポート出力、通知設定……」

ソウタはノートPCの前で、デモのスライドを並べ替えていた。来週、大手物流企業「ヤマトロジスティクス」への初めてのプロダクトデモが控えている。TechNova の AI 分析プラットフォームの全貌を見せつけるチャンスだ。

「ストップ」

カイが背後から声をかけた。ソウタのスクリーンに映った 47 枚のスライドを一瞥して、首を横に振る。

「ソウタ、それは港湾ツアーだ」

「港湾ツアー?」

「Harbor Tour。観光船に乗せて港を一周するやつ。『右手に見えますのが〜、左手に見えますのが〜』。退屈だろ?」

カイはソウタの隣に座り、スライドを全て閉じた。

デモは映画だ。脚本のないデモは観客を失う。47 枚のスライドじゃなくて、たった 1 つの『Wow』が必要なんだ」


Great Demo! メソドロジー

カイがホワイトボードに書いたのは、Peter Cohan の Great Demo! フレームワークだった。

「エンタープライズデモの世界には、2 つの代表的な方法論がある。まずは Great Demo! から教える」

Do the Last Thing First

「普通の人は、デモを『セットアップ → 操作手順 → 結果』の順番で見せる。でも Great Demo! の核心は逆だ。最終結果を最初に見せる

Loading diagram...

「映画の予告編を思い出してほしい。クライマックスの一瞬を最初に見せるだろ? あれと同じだ」

INFO

Do the Last Thing First — 顧客が最も関心を持つ「成果」を冒頭 3 分以内に提示する。機能説明はその後。この順序転換が Great Demo! の本質。

Harbor Tour アンチパターン

カイは、ソウタが準備していたデモの問題点を指摘した。

「Harbor Tour とは、製品の全機能を順番に見せていくデモのこと。開発者としては『こんなに作った』と自慢したい気持ちはわかる。でも顧客にとっては、自分に関係ない機能を延々と見せられる苦痛でしかない」

Harbor Tour(NG)Great Demo!(OK)
全機能を順番に紹介顧客の課題に関連する機能だけ
30 分以上の長尺15 分以内で核心を伝える
「これもできます」の羅列「あなたの問題をこう解決します」
機能中心価値中心
質問は最後に対話しながら進行

WARNING

Harbor Tour の罠: エンジニアは機能を見せたがる。しかし顧客が買うのは機能ではなく「課題の解決」。デモの 80% は顧客に無関係な機能紹介に費やされがちだ。


Demo2Win メソドロジー

「もう 1 つの方法論が Demo2Win だ。Robert Riefstahl が体系化した、よりエモーショナルなアプローチ」

Limbic Opening

「人間の意思決定は感情が先、論理は後。Demo2Win では最初の 30 秒で感情に訴える

ソウタは興味深そうに聞いた。

「具体的にはどうやるんですか?」

「顧客の痛みを言語化する。『毎月末、30 人の担当者が 3 日間かけて手作業で集計していませんか?』——この一言で相手は身を乗り出す」

Tell-Show-Tell パターン

Loading diagram...

Tell-Show-Tell は Demo2Win の基本構造だ」

  1. Tell(説明): 「今から、月次レポートが 3 日から 10 分に短縮される様子をお見せします」
  2. Show(実演): 実際に操作して結果を出す
  3. Tell(価値の再確認): 「ご覧の通り、10 分で完了しました。年間で 1,080 時間の工数削減です」

Value Close

「最後に、顧客固有の課題に結びつけてクローズする。『御社の月次レポート業務で、同じ効果が得られます』」

INFO

Demo2Win は B2B SaaS のエンタープライズセールスで広く採用されている。Limbic Opening で感情を掴み、Tell-Show-Tell で論理を補強し、Value Close で行動を促す。


デモ 10 のベストプラクティス

カイはホワイトボードに番号を振りながら、デモの鉄則を書き出した。

#プラクティス詳細
1顧客データでパーソナライズデモ環境に顧客の社名・業界用語・実データに近い数値を仕込む
23 回リハーサル1 回目: 流れ確認、2 回目: 時間計測、3 回目: 障害シミュレーション
3バックアッププランを用意ライブデモ → 録画 → スクリーンショット の 3 段構え
4ナラティブを制御する質問は歓迎するが、脱線したら「素晴らしい質問です。後ほど詳しく」で戻す
5マジックモーメントを仕込む「え、もうできたの?」と思わせる瞬間を最低 1 つ設計する
6質問を戦略的に扱う即答できるもの → その場で、深い技術質問 → フォローアップに回す
7沈黙を恐れないWow の後に 3 秒の間を取る。インパクトが増幅される
8操作速度を調整する速すぎると追えない。クリックの前に一拍置く
9画面をクリーンに保つ不要な通知、ブラウザタブ、デスクトップアイコンは全て非表示
10ネクストステップを明確にデモの最後に「次のアクション」を提示する

WARNING

リハーサルの 3 回ルール: 「ぶっつけ本番でもいける」は幻想。プロのデモは徹底的にリハーサルされている。3 回目のリハーサルでは、意図的にネットワークを切断したり、データを消したりして障害対応を練習する。


パーソナライズされたデモ環境の構築

「ソウタ、ヤマトロジスティクスへのデモで一番やってはいけないことは何だと思う?」

「えーと……機能を見せすぎること?」

「それもあるけど、もっと致命的なのは 『サンプル株式会社』のデータで デモすること だ」

カイの言葉にソウタは図星をつかれた。まさにそのつもりだった。

「顧客は自分の世界を見たい。自社の用語、自社のデータパターン、自社のワークフロー。それがデモに反映されているだけで、信頼度が劇的に上がる」

# db/migrate/20260601000001_create_demo_environments.rb
class CreateDemoEnvironments < ActiveRecord::Migration[7.2]
  def change
    create_table :demo_environments do |t|
      t.string :company_name, null: false
      t.string :industry, null: false
      t.string :subdomain, null: false, index: { unique: true }
      t.jsonb :branding, default: {}
      t.jsonb :terminology, default: {}
      t.datetime :expires_at, null: false
      t.string :status, default: "active"
      t.timestamps
    end
 
    create_table :feature_flags do |t|
      t.references :demo_environment, null: false, foreign_key: true
      t.string :key, null: false
      t.boolean :enabled, default: false
      t.string :description
      t.timestamps
    end
    add_index :feature_flags, [:demo_environment_id, :key], unique: true
 
    create_table :demo_scenarios do |t|
      t.references :demo_environment, null: false, foreign_key: true
      t.string :name, null: false
      t.text :description
      t.jsonb :seed_data_config, default: {}
      t.integer :position, default: 0
      t.timestamps
    end
  end
end

モデルとアソシエーション

# app/models/demo_environment.rb
class DemoEnvironment < ApplicationRecord
  has_many :feature_flags, dependent: :destroy
  has_many :demo_scenarios, dependent: :destroy
 
  validates :company_name, :industry, :subdomain, presence: true
  validates :subdomain, uniqueness: true, format: { with: /\A[a-z0-9-]+\z/ }
 
  scope :active, -> { where(status: "active").where("expires_at > ?", Time.current) }
 
  def feature_enabled?(key)
    feature_flags.find_by(key: key)&.enabled || false
  end
 
  def terminology_for(standard_term)
    terminology.fetch(standard_term, standard_term)
  end
end
# app/models/feature_flag.rb
class FeatureFlag < ApplicationRecord
  belongs_to :demo_environment
 
  validates :key, presence: true,
            uniqueness: { scope: :demo_environment_id }
 
  AVAILABLE_FLAGS = %w[
    ai_analytics
    custom_reports
    sso_integration
    api_access
    bulk_import
    real_time_dashboard
    predictive_alerts
    multi_warehouse
  ].freeze
 
  validates :key, inclusion: { in: AVAILABLE_FLAGS }
end
# app/models/demo_scenario.rb
class DemoScenario < ApplicationRecord
  belongs_to :demo_environment
 
  validates :name, presence: true
end

デモセットアップサービス

# app/services/demo_setup_service.rb
class DemoSetupService
  INDUSTRY_TEMPLATES = {
    "logistics" => {
      terminology: {
        "project" => "配送案件", "task" => "配送タスク",
        "user" => "ドライバー", "report" => "配送レポート"
      },
      features: %w[ai_analytics real_time_dashboard predictive_alerts multi_warehouse],
      scenarios: [
        { name: "月次配送分析", description: "30日間のデータからAIが最適ルートを提案",
          seed_config: { records: 5000, days: 30 } },
        { name: "リアルタイム追跡", description: "100台の車両をリアルタイム表示",
          seed_config: { vehicles: 100, active: true } }
      ]
    },
    "finance" => {
      terminology: {
        "project" => "案件", "task" => "審査タスク",
        "user" => "審査担当者", "report" => "コンプライアンスレポート"
      },
      features: %w[ai_analytics custom_reports sso_integration api_access],
      scenarios: [
        { name: "不正検知デモ", description: "取引データからAIが異常パターンを検出",
          seed_config: { transactions: 10_000, anomalies: 50 } }
      ]
    }
  }.freeze
 
  def initialize(company_name:, industry:, subdomain:, expires_in: 14.days)
    @company_name = company_name
    @industry = industry
    @subdomain = subdomain
    @expires_in = expires_in
  end
 
  def call
    ActiveRecord::Base.transaction do
      demo_env = create_environment
      setup_feature_flags(demo_env)
      setup_scenarios(demo_env)
      generate_seed_data(demo_env)
 
      demo_env
    end
  end
 
  private
 
  def create_environment
    template = INDUSTRY_TEMPLATES.fetch(@industry, {})
 
    DemoEnvironment.create!(
      company_name: @company_name,
      industry: @industry,
      subdomain: @subdomain,
      branding: default_branding,
      terminology: template.fetch(:terminology, {}),
      expires_at: @expires_in.from_now
    )
  end
 
  def setup_feature_flags(demo_env)
    template = INDUSTRY_TEMPLATES.fetch(@industry, {})
    enabled_features = template.fetch(:features, [])
 
    FeatureFlag::AVAILABLE_FLAGS.each do |flag_key|
      demo_env.feature_flags.create!(
        key: flag_key,
        enabled: enabled_features.include?(flag_key),
        description: flag_description(flag_key)
      )
    end
  end
 
  def setup_scenarios(demo_env)
    template = INDUSTRY_TEMPLATES.fetch(@industry, {})
 
    template.fetch(:scenarios, []).each_with_index do |scenario, index|
      demo_env.demo_scenarios.create!(
        name: scenario[:name],
        description: scenario[:description],
        seed_data_config: scenario[:seed_config],
        position: index
      )
    end
  end
 
  def generate_seed_data(demo_env)
    demo_env.demo_scenarios.each do |scenario|
      DemoSeedDataJob.perform_later(
        demo_environment_id: demo_env.id,
        scenario_id: scenario.id
      )
    end
  end
 
  def default_branding
    { primary_color: "#1a1a2e", accent_color: "#e94560" }
  end
 
  def flag_description(key)
    { "ai_analytics" => "AI分析", "custom_reports" => "カスタムレポート",
      "sso_integration" => "SSO連携", "api_access" => "API連携",
      "bulk_import" => "一括インポート", "real_time_dashboard" => "リアルタイム表示",
      "predictive_alerts" => "予測アラート", "multi_warehouse" => "複数拠点管理"
    }.fetch(key, key.humanize)
  end
end

フィーチャーフラグ API

# app/controllers/api/v1/feature_flags_controller.rb
module Api
  module V1
    class FeatureFlagsController < ApplicationController
      before_action :set_demo_environment
 
      def index
        flags = @demo_env.feature_flags.order(:key)
        render json: flags.map { |f|
          { key: f.key, enabled: f.enabled, description: f.description }
        }
      end
 
      def update
        flag = @demo_env.feature_flags.find_by!(key: params[:key])
        flag.update!(enabled: params[:enabled])
 
        render json: {
          key: flag.key,
          enabled: flag.enabled,
          message: "Feature '#{flag.key}' #{flag.enabled ? 'enabled' : 'disabled'}"
        }
      end
 
      private
 
      def set_demo_environment
        @demo_env = DemoEnvironment.active.find_by!(
          subdomain: request.subdomain
        )
      end
    end
  end
end

INFO

パーソナライズの効果: 顧客名・業界用語が画面に表示されているだけで、「自社のために作られた」という印象を与える。実装コストは低いが、信頼獲得の効果は絶大。


技術的障害への対処

デモの神は残酷

「さて、ソウタ。デモ当日に起こる最悪の事態は何だと思う?」

「ネットワークが落ちる……とか?」

「それもある。でもね、デモの神は残酷だ。リハーサルで完璧に動いたものが、本番で壊れる。これはもう法則だと思っていい」

カイはこれまでの経験を語った。

  • WiFi が本番会場だけ不安定だった
  • デモ用 DB のシードデータが期限切れで消えていた
  • ブラウザのアップデートで UI が崩れた
  • 顧客の会議室のプロジェクターが 4:3 比率だった

3 段階のバックアッププラン

Loading diagram...

「バックアップは 3 段階用意する」

  1. Level 1: ライブデモ(理想)
  2. Level 2: 事前録画したデモ動画(音声付き)
  3. Level 3: 主要画面のスクリーンショット(ストーリー付き)
  4. 最終手段: ホワイトボードでアーキテクチャを描きながら口頭で説明

リダイレクトテクニック

「障害が起きたとき、最悪なのはフリーズすること。そして 2 番目に悪いのはパニックで操作しまくることだ」

カイが教えたのは、Acknowledge → Pivot → Continue のフレームワーク。

  1. Acknowledge(認める): 「少し技術的な問題が発生しました」
  2. Pivot(転換する): 「ここで、先ほどの分析結果をもう少し深掘りさせてください」
  3. Continue(続ける): バックアップに切り替えて、何事もなかったかのように進行

ソウタの初デモ——そして障害

デモ当日。ソウタはカイの教えに従い、ヤマトロジスティクスのデモ環境を入念に準備していた。

# デモ前日に実行したセットアップ
service = DemoSetupService.new(
  company_name: "ヤマトロジスティクス",
  industry: "logistics",
  subdomain: "yamato-demo",
  expires_in: 7.days
)
demo_env = service.call
 
# フィーチャーフラグの最終確認
demo_env.feature_flags.each do |flag|
  puts "#{flag.key}: #{flag.enabled ? '✓' : '✗'}"
end

デモは順調に始まった。Limbic Opening で顧客の配送課題に言及し、Do the Last Thing First で AI が最適ルートを提案する画面を見せた。

顧客の物流部長が身を乗り出した瞬間——画面が真っ白になった。

「あ……」

ソウタの心臓が跳ねた。デモサーバーがタイムアウトしている。隣に座るカイが、膝の下でそっとソウタの肩を叩いた。

ソウタは深呼吸した。Acknowledge → Pivot → Continue

「少し接続が不安定のようです。ちょうどいいタイミングなので、今お見せした分析結果をもう少し詳しくご説明させてください」

ソウタはバックアップの録画デモに切り替えた。録画には、同じ分析画面がスムーズに動作する様子が収められていた。

物流部長は気にした様子もなく、質問を続けた。

「この最適ルート提案、うちの繁忙期データで試せますか?」

ソウタは内心で安堵しながら答えた。「はい、もちろんです。トライアル環境をご用意しますので、御社のデータで検証いただけます」

INFO

障害はチャンスにもなる: 障害からスムーズに復帰する姿は、「この会社は本番でもトラブル対応できる」という信頼感を生む。逆にパニックすると、製品の信頼性まで疑われる。


エンタープライズセールスの 80/20 ルール

デモ後の振り返りで、カイはもう一つの重要な教訓を伝えた。

「ソウタ、今日のデモは成功だった。でもここからが本番だ。デモはエンタープライズセールスの 20% に過ぎない

「残りの 80% は何ですか?」

「エンタープライズの現実を乗り越えること。SSO の要件、既存システムとの統合、コンプライアンス、調達プロセス——技術的に完璧なデモをしても、これらをクリアしなければ契約にはならない」

エンタープライズ顧客の関心事と FDE の備え

関心領域よくある質問FDE の準備
認証・SSO「うちの Entra ID と SAML 連携できますか?」SSO 設定の手順書、対応 IdP 一覧、テスト環境
データ移行「既存の基幹システムからデータを移せますか?」ETL パイプラインの雛形、CSV/API インポート機能
コンプライアンス「SOC2 レポートはありますか?」SOC2 Type II レポート、GDPR 対応状況、HIPAA BAA
SLA「稼働率保証は?障害時の対応は?」SLA ドキュメント、インシデント対応フロー
調達プロセス「情シスのセキュリティ審査に 3 ヶ月かかる」セキュリティチェックシートの事前回答、ペネトレーションテスト結果
既存ツール連携「Slack / Teams に通知を飛ばせますか?」Webhook 連携のデモ、API ドキュメント
スケーラビリティ「ユーザー 1 万人でも大丈夫?」負荷テスト結果、マルチテナントアーキテクチャ説明

WARNING

FDE はセールスエンジニアではない: FDE はデモだけでなく、技術的なブロッカーを自ら解決する。SSO 連携が必要なら自分でコードを書く。データ移行が必要なら ETL スクリプトを書く。「持ち帰って確認します」ではなく「今週中に対応します」が FDE の回答。


AWS によるデモ環境のアーキテクチャ

「パーソナライズされたデモ環境を、顧客ごとに用意する。そのためのインフラも設計しておこう」

カイが描いたのは、ECS + CloudFront を使った分離されたデモ環境のアーキテクチャだった。

Loading diagram...

アーキテクチャのポイント

  • 顧客ごとの ECS タスク: 各見込み顧客に専用のコンテナを起動。データの分離を保証
  • CloudFront: 顧客がどのリージョンにいても高速にアクセス可能
  • Route53: {company}-demo.technova.app のサブドメインを自動生成
  • RDS: PostgreSQL のスキーマ分離で、単一 DB インスタンスでも論理的にデータを分離

Terraform によるデモ環境プロビジョニング

# modules/demo_environment/main.tf
resource "aws_ecs_task_definition" "demo_app" {
  family                   = "demo-${var.customer_slug}"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 512
  memory                   = 1024
 
  container_definitions = jsonencode([{
    name  = "demo-app"
    image = "${var.ecr_repository_url}:${var.app_version}"
    environment = [
      { name = "DEMO_SUBDOMAIN", value = var.customer_slug },
      { name = "DATABASE_SCHEMA", value = "demo_${var.customer_slug}" },
      { name = "RAILS_ENV", value = "demo" }
    ]
    portMappings = [{ containerPort = 3000, protocol = "tcp" }]
  }])
}
 
resource "aws_cloudfront_distribution" "demo" {
  origin {
    domain_name = aws_lb.demo_alb.dns_name
    origin_id   = "demo-${var.customer_slug}"
    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "https-only"
      origin_ssl_protocols   = ["TLSv1.2"]
    }
  }
 
  enabled = true
  aliases = ["${var.customer_slug}-demo.technova.app"]
 
  default_cache_behavior {
    allowed_methods        = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = "demo-${var.customer_slug}"
    viewer_protocol_policy = "redirect-to-https"
    forwarded_values {
      query_string = true
      cookies { forward = "all" }
    }
  }
 
  restrictions { geo_restriction { restriction_type = "none" } }
  viewer_certificate {
    acm_certificate_arn = var.acm_certificate_arn
    ssl_support_method  = "sni-only"
  }
}
# app/services/demo_infrastructure_service.rb
class DemoInfrastructureService
  def initialize(demo_environment)
    @demo_env = demo_environment
  end
 
  def provision
    system("terraform", "apply",
           "-var", "customer_slug=#{@demo_env.subdomain}",
           "-var", "app_version=#{ENV.fetch('APP_VERSION', 'latest')}",
           "-auto-approve",
           chdir: Rails.root.join("terraform/modules/demo_environment"))
 
    @demo_env.update!(
      status: "provisioned",
      branding: @demo_env.branding.merge(
        url: "https://#{@demo_env.subdomain}-demo.technova.app"
      )
    )
  end
 
  def teardown
    system("terraform", "destroy",
           "-var", "customer_slug=#{@demo_env.subdomain}",
           "-auto-approve",
           chdir: Rails.root.join("terraform/modules/demo_environment"))
    @demo_env.update!(status: "deprovisioned")
  end
end

INFO

デモ環境の有効期限: expires_at を設定し、定期的に古いデモ環境を自動削除する。放置すると AWS コストが膨らむ。CronJob で毎日チェックし、期限切れのタスクを停止する運用が必須。


2026 年のデモ自動化トレンド

カイは最近のトレンドについても触れた。

「FDE がデモを 1 対 1 でやる時代は変わりつつある。2026 年のトレンドを押さえておこう」

インタラクティブプロダクトツアー

Navattic や Storylane のようなツールが普及し、見込み顧客がセルフサービスでデモを体験できるようになった。

Loading diagram...

AI 生成パーソナライズドデモ

  • 顧客の業界・規模・課題に基づいて、AI がデモシナリオを自動生成
  • デモ中の質問に AI アシスタントがリアルタイムで回答を提案
  • デモ後のフォローアップメールを AI が自動作成

サンドボックス環境とエンゲージメント分析

見込み顧客に制限付きの本番環境を提供し、自社データで試せるようにする。さらに、デモ中の顧客行動(滞在時間・クリック数・リプレイ率・社内共有数)を分析し、購買意欲のシグナルを検出する。

INFO

PLG(Product-Led Growth)とデモの融合: 2026 年、デモは「営業が見せるもの」から「顧客が自ら体験するもの」に進化している。FDE の役割は、セルフサービスデモでは解決できない複雑なエンタープライズ要件に集中することになる。


成功したデモの先にあるもの

3 週間後。ヤマトロジスティクスからの連絡が入った。

「ソウタ、来たぞ」

カイがスマートフォンの画面を見せた。ヤマトロジスティクスの CTO からのメール。

トライアル環境で自社データを使った検証を行いました。配送ルート最適化の結果に驚いています。本格導入に向けた技術検証(PoC)のスケジュールを相談させてください。

ソウタは拳を握った。

「カイさん、あの日デモが落ちたときは終わったと思いました」

「でも落ちなかっただろ?デモの神に試されただけだ」

カイは笑った。

「ソウタ、覚えておけ。デモは技術の披露じゃない。顧客の未来を見せることだ。47 枚のスライドじゃなく、たった 1 つの Wow が、顧客の心を動かす」

ソウタはノートを開き、今日学んだことを書き留めた。

デモは映画だ。
脚本のないデモは観客を失う。
最終結果を最初に見せろ。
顧客のデータで語れ。
障害は起きる。備えておけ。
そして、デモはセールスの 20% に過ぎない。

窓の外には、ヤマトロジスティクスの配送トラックが走っていた。あのトラックの走行ルートを最適化する——ソウタが見せた未来が、現実になろうとしている。