【Next.js/TypeScript】Web Share APIとURLコピーで快適な共有機能を実装する

この記事では Web Share API と URLコピー機能、そして X（Twitter）シェア を組み合わせた共有機能の実装方法を紹介します。
Next.js と TypeScript を使用しつつ、ブラウザサポート状況を考慮したフォールバックを実装していきます。

## 対応する機能

1. Web Share API（対応ブラウザのみ）
2. URLコピー（すべてのブラウザ）
3. X（Twitter）シェア（リンクベース）

## 完成イメージ

- Web Share API に対応しているブラウザの場合は、ネイティブの共有ダイアログが表示される
- どのブラウザでも URL コピーが必ずできる
- アイコンなどで視覚的にわかりやすいボタンを配置
- X（Twitter）へのシェアボタンも用意し、記事タイトルとURLを含むツイート画面を別タブで開く

## 実装コード

### ShareButtonコンポーネント

```typescript
'use client'

import { useState, useEffect } from "react"

interface ShareButtonProps {
  url: string
  title: string
}

export default function ShareButton({ url, title }: ShareButtonProps) {
  const [canShare, setCanShare] = useState(false)
  const [showCopiedMessage, setShowCopiedMessage] = useState(false)

  useEffect(() => {
    // ブラウザがNavigator.shareをサポートしているかを判定
    setCanShare(typeof navigator !== "undefined" && "share" in navigator)
  }, [])

  // === Web Share API対応の場合 ===
  const handleShare = async () => {
    try {
      await navigator.share({
        title,
        text: title,
        url,
      })
      // シェア成功・またはユーザーキャンセルでも特に何もしない
    } catch {
      // シェア失敗時は静かにキャッチ（ユーザーキャンセル等も含む）
    }
  }

  // === URLコピー機能 ===
  const handleCopyUrl = async () => {
    try {
      // まずClipboard APIが使えるか試す
      await navigator.clipboard.writeText(url)
      setShowCopiedMessage(true)
      setTimeout(() => setShowCopiedMessage(false), 2000)
    } catch {
      // Clipboard APIが使えない場合のフォールバック（execCommand）
      const textArea = document.createElement("textarea")
      textArea.value = url
      document.body.appendChild(textArea)
      textArea.select()
      try {
        document.execCommand("copy")
      } catch {
        // さらに失敗した場合も静かにキャッチ
      }
      document.body.removeChild(textArea)
      setShowCopiedMessage(true)
      setTimeout(() => setShowCopiedMessage(false), 2000)
    }
  }

  // === X(Twitter) シェア ===
  // シェア時にはタイトル・URLを組み込み、別タブで開く
  const handleShareOnTwitter = () => {
    const shareUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(title)}&url=${encodeURIComponent(url)}`
    window.open(shareUrl, "_blank", "noopener,noreferrer")
  }

  return (
    <div className="flex gap-4">
      {/* Copy URL Button - Always visible */}
      <button
        onClick={handleCopyUrl}
        className="group inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors relative"
        aria-label="Copy URL to clipboard"
      >
        {/* ここにリンクアイコン等を置く */}
        {showCopiedMessage && (
          <span className="absolute -top-8 left-1/2 -translate-x-1/2 bg-background border px-2 py-1 rounded text-sm whitespace-nowrap">
            URLをコピーしました
          </span>
        )}
      </button>

      {/* Native Share Button - Only on supported browsers */}
      {canShare && (
        <button
          onClick={handleShare}
          className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
          aria-label="Share article"
        >
          {/* ここに共有アイコン等を置く */}
        </button>
      )}

      {/* X(Twitter) Share Button */}
      <button
        onClick={handleShareOnTwitter}
        className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
        aria-label="Share on Twitter"
      >
        {/* ここにTwitterのアイコン等を置く */}
      </button>
    </div>
  )
}
```

## 実装のポイント

1. クライアントサイド実装の明示
   - `use client` ディレクティブを先頭につけることで、サーバーサイドレンダリング時に navigator が参照されるのを防ぎ、ハイドレーションエラーを回避
   - useEffect 内でブラウザAPIの存在判定を行う

2. シンプルなエラーハンドリング
   - navigator.share のエラーはユーザーのキャンセルも含むため、シンプルに静かにキャッチ
   - URLコピーでも、クリップボードAPIが使えない時は execCommand("copy") にフォールバック

3. URLコピーの多段フォールバック
   - まずは新しい navigator.clipboard を試し、失敗時は execCommand("copy") へ
   - コピー完了後は短いトーストメッセージを表示し、ユーザに成功を伝える

4. アクセシビリティの考慮
   - ボタンには aria-label を設定して用途を明示
   - アイコンは aria-hidden="true" や role="img" を状況に合わせて付与するとよい

5. SSRとの相性
   - Next.js 13以降では、サーバーコンポーネントとクライアントコンポーネントの切り分けが重要
   - 本記事のようなブラウザ機能を使う場合は、クライアントコンポーネント ('use client') が必須

## カスタマイズのポイント

1. 見た目のカスタマイズ
   Tailwind CSSを例にすると、クラスで柔軟に変更可能です。

```typescript
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
```

2. フィードバックのカスタマイズ
   コピー完了時に表示するメッセージや、表示時間を変更することも簡単です。

```typescript
setShowCopiedMessage(true)
setTimeout(() => setShowCopiedMessage(false), 2000)
```

## 動作確認

### Web Share API 対応ブラウザ
- iOS Safari (iOS13+)
- Chrome for Android
- Edge for Android など

### URLコピー＆Xシェア
- すべてのモダンブラウザで動作
- クリップボードAPIが非対応の場合でも execCommand("copy") が発動

## まとめ

- Web Share API によるネイティブUIのメリットを活かしつつ、未対応ブラウザでは URLコピー をフォールバックとして提供
- X（Twitter）シェア もリンクベースで簡単に実装できる
- Next.js (App Router) と TypeScript の組み合わせでも特に難しい設定は不要

これにより、ユーザーがどのブラウザを使用していても、シェアに関する操作をスムーズに行えるようになります。ぜひプロジェクトで活用してみてください。

## 参考文献

- [Dev.toスタイルのブログ記事レイアウトを導入してみた | tech.jugoya.ai](https://tech.jugoya.ai/blog/2025-01-13-adopt-devto-style-layout)
- [Web Share API - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API)
- [Navigator.share() - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share)
- [Can I use Web Share API?](https://caniuse.com/web-share)

もしご不明点や追加してほしい項目があれば、ぜひコメントをお寄せください！