2
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?

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

Posted at

この記事では 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コンポーネント

'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を例にすると、クラスで柔軟に変更可能です。
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors"
  1. フィードバックのカスタマイズ
    コピー完了時に表示するメッセージや、表示時間を変更することも簡単です。
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 の組み合わせでも特に難しい設定は不要

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

参考文献

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

2
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
2
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?