---
type: how-to
title: フロントエンド連携の例
summary: 素の HTML、React、Vue、Svelte、Angular、Next.js でウィジェットをマウントします。要素はどこでも同じです。
---

ウィジェットは標準のカスタム要素なので、インストールするラッパーなしであらゆるフレームワークで動きます。形はいつも同じです。パッケージを一度読み込み（[クライアント側の連携](/docs/integration-guides/cdn)）、フォームの中に `<caputchin-widget sitekey="cpt_pub_...">` をレンダリングし、訪問者がチャレンジをクリアしたときに注入される隠しの `caputchin-token` フィールドを読みます。ゲームの場合は `<caputchin-game ... game="caputchin/games/leaf-memory">` に差し替えます。

<Callout type="warn">
サイトキーでゲームゲートがオン（その **検証にゲームを必須にする** 設定）になっていると、チェックボックスの `<caputchin-widget>` はそのキーで通れません。サーバーがゲームを必須にするので、ウィジェットは `gate-unavailable` エラーを出します。ゲートされたキーでは `<caputchin-game>` を使ってください。これは設計どおりで、ゲートが Caputchin 側で強制され、チェックボックスではそれを満たせないからです。[ゲームゲート](/docs/site-keys/games) を参照してください。
</Callout>

以下の例は、各フレームワークの構文と、その 1 つのカスタム要素特有のクセだけが違います。

## 素の HTML

注入されるフィールドは通常のフォーム POST に乗っていくので、配線するものは何もありません：

```html
<script src="https://cdn.jsdelivr.net/npm/@caputchin/widget@3/dist/widget.js"></script>

<form action="/contact" method="POST">
  <input name="email" type="email" required />
  <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
  <button type="submit">Send</button>
</form>
```

## React

起動時に一度インポートし、送信時に `FormData` からフィールドを読みます：

```jsx
import "@caputchin/widget";

export function ContactForm() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    await fetch("/api/contact", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ email: data.get("email"), token: data.get("caputchin-token") }),
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <caputchin-widget sitekey="cpt_pub_..." />
      <button type="submit">Send</button>
    </form>
  );
}
```

React で TypeScript を使う場合、JSX が両方の要素を受け入れるよう、一度だけ宣言します：

```ts
// caputchin.d.ts
import type { DetailedHTMLProps, HTMLAttributes } from "react";
declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "caputchin-widget": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string }, HTMLElement>;
      "caputchin-game": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string; game?: string; games?: string }, HTMLElement>;
    }
  }
}
```

この `declare module "react"` の形は React の JSX に固有です。素の TypeScript では要素は単なる `HTMLElement` であり宣言は不要で、以下のほかのフレームワークはカスタム要素をそれぞれ独自のやり方で型付けするので、このブロックが要るのは React プロジェクトだけです。

## Vue

`caputchin-` タグがカスタム要素であると Vue のコンパイラに伝え、それらをコンポーネントとして解決しようとしないようにします：

```js
// vite.config.js
export default {
  plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith("caputchin-") } } })],
};
```

```vue
<script setup>
import "@caputchin/widget";
function onSubmit(e) {
  const data = new FormData(e.target);
  // send data.get("caputchin-token") to your backend
}
</script>

<template>
  <form @submit.prevent="onSubmit">
    <input name="email" type="email" required />
    <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
    <button type="submit">Send</button>
  </form>
</template>
```

## Svelte

Svelte はカスタム要素をそのままレンダリングします。設定は要りません：

```svelte
<script>
  import "@caputchin/widget";
  function onSubmit(e) {
    const data = new FormData(e.currentTarget);
    // send data.get("caputchin-token") to your backend
  }
</script>

<form on:submit|preventDefault={onSubmit}>
  <input name="email" type="email" required />
  <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
  <button type="submit">Send</button>
</form>
```

## Angular

Angular が未知のタグを許すよう、モジュールまたはスタンドアロンコンポーネントに `CUSTOM_ELEMENTS_SCHEMA` を追加し、パッケージを一度インポートします（`main.ts` で）：

```ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "@caputchin/widget";

@Component({
  selector: "contact-form",
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <form (submit)="onSubmit($event)">
      <input name="email" type="email" required />
      <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
      <button type="submit">Send</button>
    </form>
  `,
})
export class ContactFormComponent {
  onSubmit(e: Event) {
    const data = new FormData(e.target as HTMLFormElement);
    // send data.get("caputchin-token") to your backend
  }
}
```

## Next.js

同じ React のコードが動きます。カスタム要素はサーバーレンダリング中は空のタグとしてレンダリングされ、パッケージが読み込まれるとクライアント側でアップグレードされるので、クライアントコンポーネントで `@caputchin/widget` をインポートする（または `next/script` で CDN スクリプトを読み込む）以外に特別な扱いは要りません。

## あわせて読む

- 読み込みの選択肢については [クライアント側の連携](/docs/integration-guides/cdn)。
- バックエンド側については [サーバー側の連携](/docs/integration-guides/verify-on-your-backend)。
