Koeコエ

JavaScriptウィジェットの作り方【埋め込みスクリプト解説】

·10分で読めます·
JavaScriptウィジェットフロントエンド開発

埋め込みJavaScriptウィジェットとは

外部サイトに埋め込めるJavaScriptウィジェットとは、<script> タグを1行追加するだけで他のWebサイトにコンテンツを表示できる仕組みです。Googleアナリティクス・チャットボット・テスティモニアルウィジェットなど、多くのSaaS製品がこの形式でサードパーティ統合を提供しています。

テスティモニアルウィジェットを例にとると、顧客が自社サイトに次の2行を貼るだけでレビューが表示される仕組みです。

<div data-koe-widget="widget-123"></div>
<script src="https://koecollect.com/embed.js" async></script>

このページでは、このような埋め込みウィジェットの実装パターンを解説します。

基本的なウィジェットスクリプトの構成

シンプルな実装パターン

// embed.js
(function() {
  'use strict';

  // 設定の取得
  const containers = document.querySelectorAll('[data-koe-widget]');

  containers.forEach(function(container) {
    const widgetId = container.getAttribute('data-koe-widget');

    if (!widgetId) return;

    // APIからデータを取得
    fetch('https://api.koecollect.com/widget/' + widgetId, {
      headers: {
        'Accept': 'application/json'
      }
    })
    .then(function(response) {
      if (!response.ok) throw new Error('API error');
      return response.json();
    })
    .then(function(data) {
      renderWidget(container, data);
    })
    .catch(function(error) {
      console.warn('Koe widget failed to load:', error);
      // エラー時はコンテナを非表示にする(ページレイアウトへの影響を最小化)
      container.style.display = 'none';
    });
  });

  function renderWidget(container, data) {
    // HTMLを生成してDOMに挿入
    const html = data.testimonials.map(function(t) {
      return '<div class="koe-card">' +
        '<p class="koe-text">' + escapeHtml(t.text) + '</p>' +
        '<p class="koe-author">' + escapeHtml(t.name) + '</p>' +
        '</div>';
    }).join('');

    container.innerHTML = '<div class="koe-widget">' + html + '</div>';
    injectStyles();
  }

  function escapeHtml(str) {
    const div = document.createElement('div');
    div.appendChild(document.createTextNode(str));
    return div.innerHTML;
  }

  function injectStyles() {
    if (document.getElementById('koe-styles')) return;
    const style = document.createElement('style');
    style.id = 'koe-styles';
    style.textContent = '.koe-widget { font-family: sans-serif; }' +
      '.koe-card { border: 1px solid #eee; padding: 16px; margin: 8px 0; border-radius: 8px; }' +
      '.koe-author { font-weight: bold; margin-top: 8px; }';
    document.head.appendChild(style);
  }
})();

重要なポイント:

  • 即時実行関数(IIFE)でグローバルスコープを汚染しない
  • escapeHtml でXSSを防ぐ
  • エラーは静かに失敗させる(ページ全体を壊さない)

Shadow DOMを使ったスタイル分離

埋め込み先のCSSと干渉しないように、Shadow DOMを使ってウィジェットをカプセル化する方法です。

function renderWithShadowDOM(container, data) {
  // Shadow rootを作成
  const shadow = container.attachShadow({ mode: 'closed' });

  // スタイルをShadow DOM内に閉じ込める
  const style = document.createElement('style');
  style.textContent = `
    .koe-card {
      border: 1px solid #eee;
      padding: 16px;
      border-radius: 8px;
      font-family: -apple-system, sans-serif;
    }
  `;
  shadow.appendChild(style);

  // コンテンツを追加
  const wrapper = document.createElement('div');
  data.testimonials.forEach(function(t) {
    const card = document.createElement('div');
    card.className = 'koe-card';
    card.textContent = t.text;
    wrapper.appendChild(card);
  });

  shadow.appendChild(wrapper);
}

Shadow DOMを使うとウィジェットのCSSが完全に外部と分離されるため、埋め込み先のCSSの影響を受けません。ただし、ウィジェット側からも埋め込み先のCSSを参照できなくなります。

CORS(クロスオリジンリソース共有)の設定

埋め込みウィジェットがAPIにアクセスする場合、APIサーバー側でCORS設定が必要です。

Next.js App RouterでのCORSレスポンスヘッダーの設定例:

// app/api/widget/[widgetId]/route.ts
export async function GET(
  request: Request,
  { params }: { params: { widgetId: string } }
) {
  const data = await fetchWidgetData(params.widgetId);

  return Response.json(data, {
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET',
      'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
    },
  });
}

Access-Control-Allow-Origin: * は全てのオリジンからのアクセスを許可します。セキュリティを強化する場合は、* の代わりに許可するドメインのリストを指定します。

パフォーマンス最適化

キャッシュ戦略

ウィジェットのデータは頻繁に変わらないため、適切なキャッシュを設定することでAPIサーバーの負荷を大幅に削減できます。

  • ブラウザキャッシュ: Cache-Control: public, max-age=60(60秒間ブラウザキャッシュ)
  • CDNキャッシュ: s-maxage=300, stale-while-revalidate=600(CDNで5分キャッシュ)

スクリプトの非同期読み込み

埋め込みスクリプトには必ず async または defer を付けてください。これがないと、スクリプトの読み込みが完了するまでページのレンダリングがブロックされます。

<!-- NG: ページレンダリングをブロックする -->
<script src="https://koecollect.com/embed.js"></script>

<!-- OK: 非同期で読み込む -->
<script src="https://koecollect.com/embed.js" async></script>

DOM操作のタイミング問題

スクリプトがasyncで読み込まれる場合、DOMの状態(読み込み完了かどうか)によって動作が変わります。

// DOMの状態を確認してから実行
(function() {
  function init() {
    // ウィジェットの初期化処理
    const containers = document.querySelectorAll('[data-koe-widget]');
    // ...
  }

  if (document.readyState === 'loading') {
    // まだ読み込み中 → イベントリスナーで待機
    document.addEventListener('DOMContentLoaded', init);
  } else {
    // すでに読み込み完了 → 即実行
    init();
  }
})();

iframeとの使い分け

詳しくはWebサイトのiframeとEmbed完全ガイドを参照してください。

簡単な判断基準:

  • 外部サービス(YouTube・Google Maps)の埋め込み → iframe(提供された埋め込みコードを使う)
  • 自社ウィジェット・カスタマイズが必要なもの → JavaScriptスクリプト方式(レビューウィジェットの埋め込み方法も参考になります)

まとめ

埋め込みJavaScriptウィジェットの開発では、グローバルスコープの汚染防止・XSS対策・CORS設定・非同期読み込みの4点が特に重要です。Shadow DOMを使ったスタイル分離まで実装できれば、どの埋め込み先でも一貫した表示が保てます。顧客データを自動化するAPI連携については、こちらの記事も参考になります。

テスティモニアルの収集から管理・ウィジェット表示まで一括で対応したい場合は、Koeをご活用ください。

Koeを無料で試してみませんか?

テスティモニアルの収集・管理・表示をワンストップで。

無料で始める