Compartilhamento nativo com a Web Share API em projetos Next.js
Publicado 18 de fevereiro de 2025

Neste artigo, demonstrarei como implementei um botão de compartilhamento nativo para dispositivos móveis na página de artigos do blog do meu site pessoal usando a Web Share API e, ao mesmo tempo, mantendo os botões tradicionais de compartilhamento social para o usuário desktop.
O que é a API de compartilhamento da Web?
A Web Share API permite que os sites compartilhem textos, links e arquivos usando a caixa de diálogo de compartilhamento nativa do sistema. Essa API proporciona uma experiência mais perfeita em dispositivos móveis em comparação com os widgets de compartilhamento tradicionais de terceiros.
Por que usar a Web Share API?
- Experiência nativa no em dispositivos moveis.
- Integra-se diretamente aos aplicativos instalados
- Não há necessidade de bibliotecas JavaScript externas.
Implementação da API de compartilhamento da Web em um blog Next.js
1. Configuração do componente Share Button
No meu blog, criei um componente ShareButton para lidar com a Web Share API (para usuários móveis) e com botões de compartilhamento social (para usuários de desktop).
2. Visão geral do componente
- Para usuários de dispositivos móveis, ele exibe um botão flutuante que aciona
navigator.share(). - Para usuários de desktop, ele fornece um menu suspenso com várias opções de compartilhamento usando
next-share.
ShareButton.tsx
1'use client'
2
3import { Share2 } from 'lucide-react'
4import {
5 LinkedinShareButton,
6 TwitterShareButton,
7 EmailShareButton,
8 TelegramShareButton,
9 WhatsappShareButton,
10 FacebookShareButton,
11} from 'next-share'
12import { Button } from '@/components/ui/button'
13import {
14 DropdownMenu,
15 DropdownMenuContent,
16 DropdownMenuItem,
17 DropdownMenuTrigger,
18} from '@/components/ui/dropdown-menu'
19import { useEffect, useState } from 'react'
20
21type tParams = {
22 slug: string
23 title: string
24 body: string
25}
26
27export default function ShareButton(props: { params: tParams }) {
28 const baseUrl = process.env.NEXT_PUBLIC_BASE_URL
29 const { slug, title, body } = props.params
30
31 const shareOptions = [
32 {
33 name: 'LinkedIn',
34 Component: LinkedinShareButton,
35 url: `${baseUrl}/posts/${slug}`,
36 ariaLabel: 'Compartilhar no LinkedIn',
37 },
38 {
39 name: 'Twitter',
40 Component: TwitterShareButton,
41 url: `${baseUrl}/posts/${slug}`,
42 title: title,
43 ariaLabel: 'Compartilhar no Twitter',
44 },
45 {
46 name: 'Email',
47 Component: EmailShareButton,
48 url: `${baseUrl}/posts/${slug}`,
49 title: title,
50 body: body,
51 ariaLabel: 'Compartilhar por Email',
52 },
53 {
54 name: 'WhatsApp',
55 Component: WhatsappShareButton,
56 url: `${baseUrl}/posts/${slug}`,
57 title: title,
58 separator: ':: ',
59 ariaLabel: 'Compartilhar no WhatsApp',
60 },
61 {
62 name: 'Telegram',
63 Component: TelegramShareButton,
64 url: `${baseUrl}/posts/${slug}`,
65 title: title,
66 ariaLabel: 'Compartilhar no Telegram',
67 },
68 {
69 name: 'Facebook',
70 Component: FacebookShareButton,
71 url: `${baseUrl}/posts/${slug}`,
72 title: title,
73 ariaLabel: 'Compartilhar no Facebook',
74 },
75 ]
76
77 const [isMobile, setIsMobile] = useState(false)
78
79 useEffect(() => {
80 const checkMobile = () => {
81 if (typeof window !== 'undefined') {
82 setIsMobile(/Mobi|Android|iPhone/i.test(navigator.userAgent))
83 }
84 }
85 checkMobile()
86 }, [])
87
88 const handleNativeShare = async () => {
89 if (navigator.share) {
90 await navigator.share({
91 title,
92 text: body,
93 url: `${baseUrl}/posts/${slug}`,
94 })
95 }
96 }
97
98 return isMobile ? (
99 <Button
100 onClick={handleNativeShare}
101 variant="outline"
102 className="fixed bottom-4 right-4 rounded-full"
103 >
104 <Share2 className="h-4 w-4" />
105 </Button>
106 ) : (
107 <DropdownMenu>
108 <DropdownMenuTrigger asChild>
109 <Button
110 variant="outline"
111 size="icon"
112 className="fixed bottom-4 right-4 rounded-full"
113 >
114 <Share2 className="h-4 w-4" />
115 <span className="sr-only">Open share menu</span>
116 </Button>
117 </DropdownMenuTrigger>
118 <DropdownMenuContent align="end">
119 {shareOptions.map((option) => (
120 <DropdownMenuItem key={option.name}>
121 <option.Component
122 url={option.url}
123 title={option.title}
124 body={option.body}
125 separator={option.separator}
126 aria-label={option.ariaLabel}
127 >
128 {option.name}
129 </option.Component>
130 </DropdownMenuItem>
131 ))}
132 </DropdownMenuContent>
133 </DropdownMenu>
134 )
135}Adição do botão Share à página de post do blog
Para integrar o ShareButton à página de postagem do blog, busco os detalhes da postagem usando o Sanity.io e passo os dados relevantes para o ShareButton.
PostContent.tsx
1export default function PostContent({ params }: { params: Promise<{ slug: string }> }) {
2 const [data, setData] = useState<post | null>(null)
3 const [isLoading, setIsLoading] = useState(true)
4 const { resolvedTheme } = useTheme()
5 const router = useRouter()
6
7 useEffect(() => {
8 const fetchData = async () => {
9 setIsLoading(true)
10 const { slug } = await params
11 const fetchedData = await getData(slug)
12
13 if (!fetchedData) {
14 router.push('/not-found')
15 } else {
16 setData(fetchedData)
17 }
18 setIsLoading(false)
19 }
20 fetchData()
21 }, [params])
22
23 if (isLoading || !data) {
24 return <Loading />
25 }
26
27 const shareParams = {
28 slug: data.slug.current,
29 body: `Check out this article: ${data.title}. Read more at:`,
30 title: data.title,
31 }
32
33 return (
34 <div className="container mx-auto px-4 py-8">
35 <article className="max-w-4xl mx-auto">
36 <h1 className="text-4xl font-bold mb-4">{data.title}</h1>
37 <p className="text-muted-foreground mb-8">
38 Published {formatDate(data.firstPublishedDate)}
39 </p>
40
41 <div className="lg:grid lg:grid-cols-[1fr_250px] lg:gap-8">
42 <div className="prose prose-lg dark:prose-invert">
43 <PortableText value={data.content} components={PortableTextComponent} />
44 </div>
45 <aside className="mt-8 lg:mt-0">
46 <div className="sticky top-4">
47 <TableOfContents className="hidden lg:block" headings={data.headings} />
48 </div>
49 </aside>
50 </div>
51 </article>
52
53 <ShareButton params={shareParams} />
54 </div>
55 )
56}Conclusão
Ao implementar a Web Share API, oferecemos uma experiência de compartilhamento nativa para usuários móveis e, ao mesmo tempo, mantemos botões de compartilhamento social amigável para desktop. Essa abordagem garante:
- Integração móvel perfeita
- Melhor experiência do usuário
Se estiver criando um blog com o Next.js, considere adicionar a API Web Share para obter uma maneira moderna de compartilhar conteúdo!