Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Open Graph i udostępnianie na platformach społecznościowych

Odpowiednie udostępnianie treści w mediach społecznościowych to kluczowy element strategii marketingowej i SEO każdej nowoczesnej aplikacji. Protokoły takie jak Open Graph i Twitter Cards pozwalają na kontrolę wyglądu udostępnianych linków, co znacznie zwiększa ich atrakcyjność i skuteczność. W tym rozdziale dowiesz się, jak zoptymalizować swoją aplikację Next.js pod kątem udostępniania w mediach społecznościowych.

Czym jest Open Graph Protocol?

Open Graph Protocol (OGP) to technologia opracowana przez Facebook, która umożliwia każdej stronie internetowej stanie się bogatym obiektem w mediach społecznościowych. Gdy użytkownik udostępnia link do twojej strony na Facebooku, LinkedInie czy innych platformach społecznościowych, protokół OG określa, jakie informacje i elementy wizualne powinny być wyświetlane.

Podstawowe tagi Open Graph

Podstawowy zestaw tagów Open Graph obejmuje:

  1. og:title - tytuł treści
  2. og:type - typ treści (np. website, article, product)
  3. og:image - URL obrazu, który powinien reprezentować treść
  4. og:url - kanoniczny URL treści

Dodatkowe przydatne tagi obejmują:

  1. og:description - krótki opis treści
  2. og:site_name - nazwa witryny
  3. og:locale - język treści (np. pl_PL)

Twitter Cards

Twitter Cards to podobna technologia, specyficzna dla platformy Twitter (X), która kontroluje sposób wyświetlania udostępnionych treści. Najważniejsze tagi to:

  1. twitter:card - typ karty (summary, summary_large_image, app, player)
  2. twitter:title - tytuł treści
  3. twitter:description - opis treści
  4. twitter:image - obrazek do wyświetlenia
  5. twitter:site - nazwa konta witryny (@nazwawitryny)
  6. twitter:creator - nazwa konta autora treści (@autor)

Implementacja Open Graph w Next.js

Next.js 13+ znacznie upraszcza implementację protokołu Open Graph poprzez nowe API Metadanych. Oto jak to zrobić:

Podstawowa implementacja

1// app/page.tsx
2import { Metadata } from 'next';
3
4export const metadata: Metadata = {
5  openGraph: {
6    title: 'Moja aplikacja Next.js',
7    description: 'Poznaj możliwości naszej aplikacji',
8    url: 'https://twojadomena.pl',
9    siteName: 'Nazwa Twojej Strony',
10    images: [
11      {
12        url: 'https://twojadomena.pl/og-image.jpg',
13        width: 1200,
14        height: 630,
15        alt: 'Moja aplikacja Next.js',
16      },
17    ],
18    locale: 'pl_PL',
19    type: 'website',
20  },
21  twitter: {
22    card: 'summary_large_image',
23    title: 'Moja aplikacja Next.js',
24    description: 'Poznaj możliwości naszej aplikacji',
25    images: ['https://twojadomena.pl/twitter-image.jpg'],
26    creator: '@twojanazwa',
27    site: '@twojastrona',
28  },
29};
30
31export default function Home() {
32  return (
33    <main>
34      {/* Treść strony */}
35    </main>
36  );
37}

Dynamiczne metadane Open Graph

Dla stron dynamicznych, takich jak artykuły bloga czy produkty, możemy generować metadane Open Graph na podstawie danych z API lub bazy danych:

1// app/blog/[slug]/page.tsx
2import { Metadata } from 'next';
3import { getBlogPost } from '@/lib/api';
4
5export async function generateMetadata({ params }): Promise<Metadata> {
6  const post = await getBlogPost((await params).slug);
7
8  if (!post) {
9    return {
10      openGraph: {
11        title: 'Post nie znaleziony',
12        description: 'Przepraszamy, nie znaleźliśmy szukanego artykułu',
13        type: 'article',
14      },
15    };
16  }
17
18  // Podstawowe informacje
19  const title = `${post.title} | Blog Moja Aplikacja`;
20  const description = post.excerpt;
21  const url = `https://twojadomena.pl/blog/${post.slug}`;
22
23  // Metadane Open Graph dla artykułu
24  return {
25    openGraph: {
26      title: title,
27      description: description,
28      url: url,
29      siteName: 'Nazwa Twojej Strony',
30      images: [
31        {
32          url: post.coverImage || 'https://twojadomena.pl/default-og.jpg',
33          width: 1200,
34          height: 630,
35          alt: post.title,
36        },
37      ],
38      locale: 'pl_PL',
39      type: 'article',
40      publishedTime: post.publishedAt,
41      authors: post.author.name,
42      tags: post.tags,
43    },
44    twitter: {
45      card: 'summary_large_image',
46      title: title,
47      description: description,
48      images: [post.coverImage || 'https://twojadomena.pl/default-twitter.jpg'],
49      creator: post.author.twitter || '@twojanazwa',
50      site: '@twojastrona',
51    },
52  };
53}
54
55export default async function BlogPost({ params }) {
56  const post = await getBlogPost((await params).slug);
57  
58  if (!post) {
59    return <div>Post nie znaleziony</div>;
60  }
61  
62  return (
63    <article>
64      <h1>{post.title}</h1>
65      <div dangerouslySetInnerHTML={{ __html: post.content }} />
66    </article>
67  );
68}

Tworzenie i optymalizacja obrazów dla mediów społecznościowych

Każda platforma społecznościowa ma swoje zalecane wymiary obrazów. Przygotowanie obrazów zgodnie z tymi wytycznymi zapewnia najlepsze doświadczenie podczas udostępniania:

Zalecane wymiary obrazów

  1. Facebook/Open Graph:

    • Zalecane: 1200 × 630 pikseli
    • Minimalnie: 600 × 315 pikseli
    • Proporcje: 1.91:1
  2. Twitter:

    • Duża karta (summary_large_image): 1200 × 628 pikseli
    • Zwykła karta (summary): 800 × 418 pikseli
  3. LinkedIn:

    • Zalecane: 1200 × 627 pikseli
    • Proporcje: 1.91:1
  4. Pinterest:

    • Zalecane: 1000 × 1500 pikseli
    • Proporcje: 2:3

Dynamiczna generacja obrazów Open Graph

Zamiast ręcznie tworzyć obrazy dla każdej strony, możemy generować je dynamicznie przy użyciu API Routes w Next.js. To świetne rozwiązanie dla aplikacji z dużą liczbą dynamicznych stron, takich jak e-commerce czy blogi:

1// app/api/og/route.tsx
2import { ImageResponse } from 'next/og';
3import { NextRequest } from 'next/server';
4
5export const runtime = 'edge';
6
7export async function GET(req: NextRequest) {
8  const { searchParams } = new URL(req.url);
9  
10  // Pobierz parametry z URL
11  const title = searchParams.get('title') || 'Moja Aplikacja';
12  const description = searchParams.get('description') || 'Domyślny opis';
13  const imageUrl = searchParams.get('image');
14  
15  // Określ kolory, czcionki i styl
16  const bgColor = '#111827';
17  const textColor = '#ffffff';
18  
19  // Można również pobierać czcionki dynamicznie
20  // const font = await fetch('...').then(res => res.arrayBuffer());
21  
22  return new ImageResponse(
23    (
24      <div
25        style={{
26          display: 'flex',
27          flexDirection: 'column',
28          alignItems: 'center',
29          justifyContent: 'center',
30          width: '100%',
31          height: '100%',
32          backgroundColor: bgColor,
33          color: textColor,
34          padding: '40px',
35          position: 'relative',
36        }}
37      >
38        {imageUrl && (
39          <img 
40            src={imageUrl} 
41            style={{ 
42              position: 'absolute',
43              top: 0,
44              left: 0,
45              width: '100%',
46              height: '100%',
47              objectFit: 'cover',
48              opacity: 0.6,
49            }} 
50          />
51        )}
52        
53        <div 
54          style={{
55            display: 'flex',
56            flexDirection: 'column',
57            zIndex: 10,
58            maxWidth: '80%',
59            textAlign: 'center',
60          }}
61        >
62          <h1 style={{ fontSize: '64px', fontWeight: 'bold', marginBottom: '20px' }}>
63            {title}
64          </h1>
65          
66          <p style={{ fontSize: '32px', margin: 0 }}>
67            {description}
68          </p>
69          
70          <div style={{ 
71            marginTop: '40px', 
72            display: 'flex', 
73            alignItems: 'center', 
74            justifyContent: 'center' 
75          }}>
76            <img 
77              src="https://twojadomena.pl/logo.png" 
78              width="60" 
79              height="60" 
80              style={{ marginRight: '20px' }} 
81            />
82            <p style={{ fontSize: '36px', fontWeight: 'bold' }}>mojadomena.pl</p>
83          </div>
84        </div>
85      </div>
86    ),
87    {
88      width: 1200,
89      height: 630,
90      // Opcjonalnie możesz zastosować czcionki
91      // fonts: [{ name: 'Inter', data: font, weight: 400 }],
92    },
93  );
94}

Używanie dynamicznie generowanych obrazów Open Graph:

1// app/blog/[slug]/page.tsx
2export async function generateMetadata({ params }) {
3  const post = await getBlogPost((await params).slug);
4  
5  const ogImageUrl = new URL('/api/og', 'https://twojadomena.pl');
6  ogImageUrl.searchParams.append('title', post.title);
7  ogImageUrl.searchParams.append('description', post.excerpt);
8  if (post.coverImage) {
9    ogImageUrl.searchParams.append('image', post.coverImage);
10  }
11  
12  return {
13    openGraph: {
14      // ...inne metadane
15      images: [
16        {
17          url: ogImageUrl.toString(),
18          width: 1200,
19          height: 630,
20          alt: post.title,
21        },
22      ],
23    },
24    //
25  };
26}

Przyciski udostępniania w mediach społecznościowych

Dodanie przycisków udostępniania do twojej aplikacji to skuteczny sposób na zwiększenie zasięgu. Oto jak zaimplementować je w Next.js:

Komponent ShareButtons

1// components/ShareButtons.tsx
2'use client';
3
4import { usePathname } from 'next/navigation';
5import { FaFacebook, FaTwitter, FaLinkedin, FaPinterest, FaEnvelope } from 'react-icons/fa';
6
7interface ShareButtonsProps {
8  title: string;
9  description?: string;
10  imageUrl?: string;
11  hashtags?: string[];
12}
13
14export default function ShareButtons({ title, description, imageUrl, hashtags = [] }: ShareButtonsProps) {
15  const pathname = usePathname();
16  
17  // Tworzymy pełny adres URL
18  const baseUrl = 'https://twojadomena.pl';
19  const url = baseUrl + pathname;
20  
21  // Kodujemy parametry do udostępniania
22  const encodedUrl = encodeURIComponent(url);
23  const encodedTitle = encodeURIComponent(title);
24  const encodedDescription = description ? encodeURIComponent(description) : '';
25  const encodedHashtags = hashtags.length > 0 ? encodeURIComponent(hashtags.join(',')) : '';
26  
27  // Tworzymy linki do udostępniania
28  const facebookShareUrl = `https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`;
29  const twitterShareUrl = `https://twitter.com/intent/tweet?url=${encodedUrl}&text=${encodedTitle}&hashtags=${encodedHashtags}`;
30  const linkedinShareUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`;
31  const pinterestShareUrl = imageUrl 
32    ? `https://pinterest.com/pin/create/button/?url=${encodedUrl}&media=${encodeURIComponent(imageUrl)}&description=${encodedTitle}`
33    : `https://pinterest.com/pin/create/button/?url=${encodedUrl}&description=${encodedTitle}`;
34  const emailShareUrl = `mailto:?subject=${encodedTitle}&body=${encodedDescription}%0A%0A${encodedUrl}`;
35  
36  // Funkcja obsługująca kliknięcie w przycisk udostępniania
37  const handleShare = (url: string) => {
38    window.open(url, '_blank', 'width=600,height=400');
39  };
40  
41  return (
42    <div className="flex space-x-4 items-center">
43      <span className="text-sm font-medium text-gray-700">Udostępnij: </span>
44      
45      <button
46        className="text-blue-600 hover:text-blue-800 transition-colors"
47        onClick={() => handleShare(facebookShareUrl)}
48        aria-label="Udostępnij na Facebooku"
49      >
50        <FaFacebook size={24} />
51      </button>
52      
53      <button
54        className="text-sky-500 hover:text-sky-700 transition-colors"
55        onClick={() => handleShare(twitterShareUrl)}
56        aria-label="Udostępnij na Twitterze"
57      >
58        <FaTwitter size={24} />
59      </button>
60      
61      <button
62        className="text-blue-700 hover:text-blue-900 transition-colors"
63        onClick={() => handleShare(linkedinShareUrl)}
64        aria-label="Udostępnij na LinkedIn"
65      >
66        <FaLinkedin size={24} />
67      </button>
68      
69      <button
70        className="text-red-600 hover:text-red-800 transition-colors"
71        onClick={() => handleShare(pinterestShareUrl)}
72        aria-label="Udostępnij na Pinterest"
73      >
74        <FaPinterest size={24} />
75      </button>
76      
77      <a
78        href={emailShareUrl}
79        className="text-gray-600 hover:text-gray-800 transition-colors"
80        aria-label="Udostępnij przez email"
81      >
82        <FaEnvelope size={24} />
83      </a>
84    </div>
85  );
86}

Użycie komponentu ShareButtons

1// app/blog/[slug]/page.tsx
2import ShareButtons from '@/components/ShareButtons';
3import { getBlogPost } from '@/lib/api';
4
5export default async function BlogPost({ params }) {
6  const post = await getBlogPost((await params).slug);
7  
8  if (!post) {
9    return <div>Post nie znaleziony</div>;
10  }
11  
12  return (
13    <article className="max-w-2xl mx-auto py-8">
14      <h1 className="text-3xl font-bold mb-4">{post.title}</h1>
15      
16      <div className="my-6">
17        <ShareButtons 
18          title={post.title}
19          description={post.excerpt}
20          imageUrl={post.coverImage}
21          hashtags={post.tags}
22        />
23      </div>
24      
25      <div className="prose max-w-none" dangerouslySetInnerHTML={{ __html: post.content }} />
26      
27      <div className="mt-8 pt-4 border-t">
28        <ShareButtons 
29          title={post.title}
30          description={post.excerpt}
31          imageUrl={post.coverImage}
32          hashtags={post.tags}
33        />
34      </div>
35    </article>
36  );
37}

Integracja z Web Share API

Web Share API to nowoczesna alternatywa dla tradycyjnych przycisków udostępniania, która pozwala użytkownikom udostępniać treści za pomocą dowolnej aplikacji zainstalowanej na ich urządzeniu:

1// components/NativeShareButton.tsx
2'use client';
3
4import { useState, useEffect } from 'react';
5import { FaShare } from 'react-icons/fa';
6import { usePathname } from 'next/navigation';
7
8interface NativeShareProps {
9  title: string;
10  text?: string;
11  url?: string;
12  files?: File[];
13}
14
15export default function NativeShareButton({ title, text, url, files }: NativeShareProps) {
16  const [isShareSupported, setIsShareSupported] = useState(false);
17  const pathname = usePathname();
18  
19  useEffect(() => {
20    // Sprawdź czy przeglądarka wspiera Web Share API
21    setIsShareSupported(
22      typeof navigator !== 'undefined' && 
23      !!navigator.share
24    );
25  }, []);
26  
27  // Jeśli przeglądarka nie wspiera Web Share API, nie renderuj przycisku
28  if (!isShareSupported) {
29    return null;
30  }
31  
32  const handleShare = async () => {
33    try {
34      const shareData: ShareData = {
35        title,
36        text: text || title,
37        url: url || window.location.href,
38      };
39      
40      // Dodaj pliki, jeśli są dostępne i przeglądarka je obsługuje
41      if (files && navigator.canShare && navigator.canShare({ files })) {
42        shareData.files = files;
43      }
44      
45      await navigator.share(shareData);
46      console.log('Treść została udostępniona');
47    } catch (error) {
48      console.error('Błąd podczas udostępniania:', error);
49    }
50  };
51  
52  return (
53    <button
54      onClick={handleShare}
55      className="flex items-center space-x-2 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors"
56      aria-label="Udostępnij"
57    >
58      <FaShare />
59      <span>Udostępnij</span>
60    </button>
61  );
62}

Testowanie Open Graph i udostępniania

Poprawne skonfigurowanie Open Graph i Twitter Cards wymaga testowania. Oto narzędzia, które mogą pomóc:

Narzędzia debugowania

  1. Facebook Sharing Debugger: https://developers.facebook.com/tools/debug/
  2. Twitter Card Validator: https://cards-dev.twitter.com/validator
  3. LinkedIn Post Inspector: https://www.linkedin.com/post-inspector/
  4. Pinterest Rich Pins Validator: https://developers.pinterest.com/tools/url-debugger/

Automatyzacja testów Open Graph

Możesz zautomatyzować testy Open Graph w ramach procesu CI/CD:

1// scripts/test-og-tags.mjs
2import puppeteer from 'puppeteer';
3import { promises as fs } from 'fs';
4
5const pages = [
6  { url: '/', name: 'home' },
7  { url: '/blog/example-post', name: 'blog-post' },
8  { url: '/products/example-product', name: 'product' },
9  // Dodaj inne ważne strony
10];
11
12async function testOgTags() {
13  const browser = await puppeteer.launch();
14  const results = [];
15  
16  try {
17    for (const page of pages) {
18      const pageUrl = `https://twojadomena.pl${page.url}`;
19      const puppeteerPage = await browser.newPage();
20      await puppeteerPage.goto(pageUrl, { waitUntil: 'networkidle0' });
21      
22      // Pobierz wszystkie tagi Open Graph
23      const ogTags = await puppeteerPage.evaluate(() => {
24        const tags = {};
25        const metaTags = document.querySelectorAll('meta[property^="og:"], meta[name^="twitter:"]');
26        
27        metaTags.forEach(tag => {
28          const property = tag.getAttribute('property') || tag.getAttribute('name');
29          const content = tag.getAttribute('content');
30          tags[property] = content;
31        });
32        
33        return tags;
34      });
35      
36      // Sprawdź wymagane tagi
37      const requiredTags = ['og:title', 'og:description', 'og:image', 'og:url', 'twitter:card'];
38      const missingTags = requiredTags.filter(tag => !ogTags[tag]);
39      
40      const result = {
41        page: page.name,
42        url: pageUrl,
43        ogTags,
44        missingTags,
45        hasErrors: missingTags.length > 0,
46      };
47      
48      results.push(result);
49      
50      await puppeteerPage.close();
51    }
52    
53    // Zapisz raport
54    await fs.writeFile('./og-tags-report.json', JSON.stringify(results, null, 2));
55    
56    // Czy wszystkie strony mają wymagane tagi
57    const hasErrors = results.some(result => result.hasErrors);
58    
59    if (hasErrors) {
60      console.error('❌ Znaleziono braki w tagach Open Graph:');
61      
62      for (const result of results) {
63        if (result.hasErrors) {
64          console.error(`Strona: ${result.page}`);
65          console.error(`Brakujące tagi: ${result.missingTags.join(', ')}`);
66          console.error('---');
67        }
68      }
69      
70      process.exit(1);
71    } else {
72      console.log('✅ Wszystkie wymagane tagi Open Graph są obecne.');
73    }
74  } finally {
75    await browser.close();
76  }
77}
78
79testOgTags().catch(console.error);

Najlepsze praktyki i porady

  1. Optymalizuj obrazy - Używaj obrazów o odpowiednich wymiarach i proporcjach dla każdej platformy
  2. Używaj unikalnych obrazów - Twórz unikalne i przyciągające wzrok obrazy dla ważnych stron
  3. Dodaj branding - Umieść logo lub elementy identyfikacji wizualnej na obrazach OG
  4. Testuj na różnych platformach - Sprawdź, jak udostępniane treści wyglądają na wszystkich najważniejszych platformach
  5. Eksperymentuj z tytułami - Testuj różne tytuły i opisy, aby zwiększyć CTR z udostępnień
  6. Aktualizuj metadane - Regularnie odświeżaj metadane dla stron z cyklicznymi treściami

Podsumowanie

Odpowiednia implementacja protokołów Open Graph i Twitter Cards oraz dobrze zaprojektowane przyciski udostępniania mogą znacząco zwiększyć widoczność i zasięg twojej aplikacji Next.js. Wykorzystanie dynamicznie generowanych obrazów OG pozwala na spersonalizowane i atrakcyjne wyświetlanie udostępnianych treści, a Web Share API oferuje natywne doświadczenie udostępniania na urządzeniach mobilnych.

Pamiętaj, że platformy społecznościowe często aktualizują swoje wymagania i zalecenia, dlatego warto regularnie sprawdzać dokumentację każdej platformy i testować udostępnianie treści na różnych urządzeniach i przeglądarkach.

Przejdź do CodeWorlds