Skip to content

Commit

Permalink
feat(seo): update meta tags and add structured data
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Jun 16, 2021
1 parent ea942a4 commit 2d32ecf
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 20 deletions.
1 change: 1 addition & 0 deletions .commitlintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
'search',
'watchhistory',
'favorites',
'seo',
],
],
},
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ The allowed scopes are:
- search
- watchhistory
- favorites
- seo

### Subject

Expand Down
2 changes: 1 addition & 1 deletion public/config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "Kfo1Se0r",
"siteName": "JW Showcase",
"description": "JW Showcase is an open-source, dynamically generated video website built around JW Player and JW Platform services. It enables you to easily publish your JW Player-hosted video content with no coding and minimal configuration.",
"description": "JW Showcase is an open-source, dynamically generated video website.",
"footerText": "Powered by JW Player",
"player": "TEMcdMw2",
"recommendationsPlaylist": "wZuMVmMk",
Expand Down
3 changes: 0 additions & 3 deletions src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,8 @@ const Layout: FC<LayoutProps> = ({ children }) => {
<meta name="description" content={description} />
<meta property="og:description" content={description} />
<meta property="og:title" content={siteName} />
{banner && <meta property="og:image" content={banner?.replace(/^https:/, 'http:')} />}
{banner && <meta property="og:image:secure_url" content={banner?.replace(/^http:/, 'https:')} />}
<meta name="twitter:title" content={siteName} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={banner} />
</Helmet>
<div className={styles.main}>
{hasDynamicBlur && blurImage && <DynamicBlur url={blurImage} transitionTime={1} debounceTime={350} />}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Root/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const Root: FC<Props> = ({ error }: Props) => {
<Route path="/" component={Home} exact />
<Route path="/p/:id" component={Playlist} exact />
<Route path="/u" component={Settings} exact />
<Route path="/m/:id/:slug" component={Movie} exact />
<Route path="/s/:id/:slug" component={Series} />
<Route path="/m/:id/:slug?" component={Movie} exact />
<Route path="/s/:id/:slug?" component={Series} />
<Route path="/q/:query?" component={Search} />
</Switch>
</Layout>
Expand Down
15 changes: 9 additions & 6 deletions src/screens/Movie/Movie.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,27 @@ const Movie = ({
if (error) return <p>Error loading list</p>;
if (!item) return <p>Can not find medium</p>;

const pageTitle = `${item.title} - ${config.siteName}`;
const canonicalUrl = item ? `${window.location.origin}${movieURL(item)}` : window.location.href;

return (
<React.Fragment>
<Helmet>
<title>{item.title} - {config.siteName}</title>
{item ? <link rel="canonical" href={`${window.location.origin}${movieURL(item)}`} /> : null}
<title>pageTitle</title>
<link rel="canonical" href={canonicalUrl} />
<meta name="description" content={item.description} />
<meta property="og:description" content={item.description} />
<meta property="og:title" content={`${item.title} - ${config.siteName}`} />
<meta property="og:title" content={pageTitle} />
<meta property="og:type" content="video.other" />
{item.image && <meta property="og:image" content={item.image?.replace(/^https:/, 'http:')} />}
{item.image && <meta property="og:image:secure_url" content={item.image?.replace(/^http:/, 'https:')} />}
<meta property="og:image:width" content={item.image ? '720' : ''} />
<meta property="og:image:height" content={item.image ? '406' : ''} />
<meta name="twitter:title" content={`${item.title} - ${config.siteName}`} />
<meta name="twitter:title" content={pageTitle} />
<meta name="twitter:description" content={item.description} />
<meta name="twitter:image" content={item.image} />
<meta property="og:video" content={window.location.href} />
<meta property="og:video:secure_url" content={window.location.href} />
<meta property="og:video" content={canonicalUrl.replace(/^https:/, 'http:')} />
<meta property="og:video:secure_url" content={canonicalUrl.replace(/^http:/, 'https:')} />
<meta property="og:video:type" content="text/html" />
<meta property="og:video:width" content="1280" />
<meta property="og:video:height" content="720" />
Expand Down
6 changes: 5 additions & 1 deletion src/screens/Playlist/Playlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@ function Playlist({
return <ErrorPage title="Playlist not found!" />;
}

const pageTitle = `${title} - ${config.siteName}`;

return (
<div className={styles.playlist}>
<Helmet>
<title>{title} - {config.siteName}</title>
<title>{pageTitle}</title>
<meta property="og:title" content={pageTitle} />
<meta name="twitter:title" content={pageTitle} />
</Helmet>
<header className={styles.header}>
<h2>{title}</h2>
Expand Down
17 changes: 10 additions & 7 deletions src/screens/Series/Series.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,24 +73,27 @@ const Series = (
if (error || playlistError) return <p>Error loading list</p>;
if (!seriesPlaylist || !item) return <p>Can not find medium</p>;

const pageTitle = `${item.title} - ${config.siteName}`;
const canonicalUrl = seriesPlaylist && item ? `${window.location.origin}${episodeURL(seriesPlaylist, item.mediaid)}` : window.location.href;

return (
<React.Fragment>
<Helmet>
<title>{item.title} - {config.siteName}</title>
{seriesPlaylist && item ? <link rel="canonical" href={`${window.location.origin}${episodeURL(seriesPlaylist, item.mediaid)}`} /> : null}
<title>{pageTitle}</title>
<link rel="canonical" href={canonicalUrl} />
<meta name="description" content={item.description} />
<meta property="og:description" content={item.description} />
<meta property="og:title" content={`${item.title} - ${config.siteName}`} />
<meta property="og:type" content="video.other" />
<meta property="og:title" content={pageTitle} />
<meta property="og:type" content="video.episode" />
{item.image && <meta property="og:image" content={item.image?.replace(/^https:/, 'http:')} />}
{item.image && <meta property="og:image:secure_url" content={item.image?.replace(/^http:/, 'https:')} />}
<meta property="og:image:width" content={item.image ? '720' : ''} />
<meta property="og:image:height" content={item.image ? '406' : ''} />
<meta name="twitter:title" content={`${item.title} - ${config.siteName}`} />
<meta name="twitter:title" content={pageTitle} />
<meta name="twitter:description" content={item.description} />
<meta name="twitter:image" content={item.image} />
<meta property="og:video" content={window.location.href} />
<meta property="og:video:secure_url" content={window.location.href} />
<meta property="og:video" content={canonicalUrl.replace(/^https:/, 'http:')} />
<meta property="og:video:secure_url" content={canonicalUrl.replace(/^http:/, 'https:')} />
<meta property="og:video:type" content="text/html" />
<meta property="og:video:width" content="1280" />
<meta property="og:video:height" content="720" />
Expand Down
88 changes: 88 additions & 0 deletions test/seo_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
Feature('seo').tag('@desktop');

Scenario('It renders the correct meta tags for the home screen', ({ I }) => {
I.amOnPage('http://localhost:8080');
I.seeInTitle('JW Showcase');

I.seeAttributesOnElements('meta[name="description"]', { content: 'JW Showcase is an open-source, dynamically generated video website.' });

I.seeAttributesOnElements('meta[property="og:title"]', { content: 'JW Showcase' });
I.seeAttributesOnElements('meta[property="og:description"]', { content: 'JW Showcase is an open-source, dynamically generated video website.' });

I.seeAttributesOnElements('meta[name="twitter:title"]', { content: 'JW Showcase' });
I.seeAttributesOnElements('meta[name="twitter:description"]', { content: 'JW Showcase is an open-source, dynamically generated video website.' });
});

Scenario('It renders the correct meta tags for the playlist screen', ({ I }) => {
I.amOnPage('http://localhost:8080/p/4fgzPjpv');
I.seeInTitle('Featured Covers - JW Showcase');

I.seeAttributesOnElements('meta[name="description"]', { content: 'JW Showcase is an open-source, dynamically generated video website.' });

I.seeAttributesOnElements('meta[property="og:title"]', { content: 'Featured Covers - JW Showcase' });
I.seeAttributesOnElements('meta[property="og:description"]', { content: 'JW Showcase is an open-source, dynamically generated video website.' });

I.seeAttributesOnElements('meta[name="twitter:title"]', { content: 'Featured Covers - JW Showcase' });
I.seeAttributesOnElements('meta[name="twitter:description"]', { content: 'JW Showcase is an open-source, dynamically generated video website.' });
});

Scenario('It renders the correct meta tags for the movie screen', ({ I }) => {
I.amOnPage('http://localhost:8080/m/XGW7EbhZ/the-spongebob-movie-sponge-on-the-run');
I.seeInTitle('The Spongebob Movie: Sponge On The Run - JW Showcase');

I.seeAttributesOnElements('meta[name="description"]', { content: 'This Memorial Day weekend, SpongeBob SquarePants, his best friend Patrick Star and the rest of the gang from Bikini Bottom hit the big screen in the first-ever all CGI SpongeBob motion picture event. After SpongeBob’s beloved pet snail Gary is snail-napped, he and Patrick embark on an epic adventure to The Lost City of Atlantic City to bring Gary home. As they navigate the delights and dangers on this perilous and hilarious rescue mission, SpongeBob and his pals prove there’s nothing stronger than the power of friendship.' });

I.seeAttributesOnElements('meta[property="og:title"]', { content: 'The Spongebob Movie: Sponge On The Run - JW Showcase' });
I.seeAttributesOnElements('meta[property="og:description"]', { content: 'This Memorial Day weekend, SpongeBob SquarePants, his best friend Patrick Star and the rest of the gang from Bikini Bottom hit the big screen in the first-ever all CGI SpongeBob motion picture event. After SpongeBob’s beloved pet snail Gary is snail-napped, he and Patrick embark on an epic adventure to The Lost City of Atlantic City to bring Gary home. As they navigate the delights and dangers on this perilous and hilarious rescue mission, SpongeBob and his pals prove there’s nothing stronger than the power of friendship.' });
I.seeAttributesOnElements('meta[property="og:type"]', { content: 'video.other' });
I.seeAttributesOnElements('meta[property="og:image"]', { content: 'http://content.jwplatform.com/v2/media/XGW7EbhZ/poster.jpg?width=720' });
I.seeAttributesOnElements('meta[property="og:image:secure_url"]', { content: 'https://content.jwplatform.com/v2/media/XGW7EbhZ/poster.jpg?width=720' });
I.seeAttributesOnElements('meta[property="og:image:width"]', { content: '720' });
I.seeAttributesOnElements('meta[property="og:image:height"]', { content: '406' });

I.seeAttributesOnElements('meta[property="og:video"]', { content: 'http://localhost:8080/m/XGW7EbhZ/the-spongebob-movie-sponge-on-the-run' });
I.seeAttributesOnElements('meta[property="og:video:secure_url"]', { content: 'https://localhost:8080/m/XGW7EbhZ/the-spongebob-movie-sponge-on-the-run' });
I.seeAttributesOnElements('meta[property="og:video:type"]', { content: 'text/html' });
I.seeAttributesOnElements('meta[property="og:video:width"]', { content: '1280' });
I.seeAttributesOnElements('meta[property="og:video:height"]', { content: '720' });

I.seeAttributesOnElements('meta[name="twitter:title"]', { content: 'The Spongebob Movie: Sponge On The Run - JW Showcase' });
I.seeAttributesOnElements('meta[name="twitter:description"]', { content: 'This Memorial Day weekend, SpongeBob SquarePants, his best friend Patrick Star and the rest of the gang from Bikini Bottom hit the big screen in the first-ever all CGI SpongeBob motion picture event. After SpongeBob’s beloved pet snail Gary is snail-napped, he and Patrick embark on an epic adventure to The Lost City of Atlantic City to bring Gary home. As they navigate the delights and dangers on this perilous and hilarious rescue mission, SpongeBob and his pals prove there’s nothing stronger than the power of friendship.' });
I.seeAttributesOnElements('meta[name="twitter:image"]', { content: 'https://content.jwplatform.com/v2/media/XGW7EbhZ/poster.jpg?width=720' });
});

Scenario('It renders the correct structured metadata for the movie screen', ({ I }) => {
I.amOnPage('http://localhost:8080/m/XGW7EbhZ/the-spongebob-movie-sponge-on-the-run');
I.seeTextEquals('{"@context":"http://schema.org/","@type":"VideoObject","@id":"http://localhost:8080//m/XGW7EbhZ/the-spongebob-movie-sponge-on-the-run","name":"The Spongebob Movie: Sponge On The Run","description":"This Memorial Day weekend, SpongeBob SquarePants, his best friend Patrick Star and the rest of the gang from Bikini Bottom hit the big screen in the first-ever all CGI SpongeBob motion picture event. After SpongeBob’s beloved pet snail Gary is snail-napped, he and Patrick embark on an epic adventure to The Lost City of Atlantic City to bring Gary home. As they navigate the delights and dangers on this perilous and hilarious rescue mission, SpongeBob and his pals prove there’s nothing stronger than the power of friendship.","duration":"PT2M","thumbnailUrl":"https://content.jwplatform.com/v2/media/XGW7EbhZ/poster.jpg?width=720","uploadDate":"2019-11-22T18:49:49.000Z"}', { css: 'script[type="application/ld+json"]' })
});

Scenario('It renders the correct meta tags for the series screen', ({ I }) => {
I.amOnPage('http://localhost:8080/s/xdAqW8ya/primitive-animals?e=xdAqW8ya');
I.seeInTitle('Blocking - JW Showcase');

I.seeAttributesOnElements('meta[name="description"]', { content: 'If you\'re brand new to Blender or are getting stuck, check out the Blender 2.8 Fundamentals series.' });

I.seeAttributesOnElements('meta[property="og:title"]', { content: 'Blocking - JW Showcase' });
I.seeAttributesOnElements('meta[property="og:description"]', { content: 'If you\'re brand new to Blender or are getting stuck, check out the Blender 2.8 Fundamentals series.' });
I.seeAttributesOnElements('meta[property="og:type"]', { content: 'video.episode' });

I.seeAttributesOnElements('meta[property="og:image"]', { content: 'http://content.jwplatform.com/v2/media/zKT3MFut/poster.jpg?width=720' });
I.seeAttributesOnElements('meta[property="og:image:secure_url"]', { content: 'https://content.jwplatform.com/v2/media/zKT3MFut/poster.jpg?width=720' });
I.seeAttributesOnElements('meta[property="og:image:width"]', { content: '720' });
I.seeAttributesOnElements('meta[property="og:image:height"]', { content: '406' });

I.seeAttributesOnElements('meta[property="og:video"]', { content: 'http://localhost:8080/s/xdAqW8ya/primitive-animals?e=zKT3MFut' });
I.seeAttributesOnElements('meta[property="og:video:secure_url"]', { content: 'https://localhost:8080/s/xdAqW8ya/primitive-animals?e=zKT3MFut' });
I.seeAttributesOnElements('meta[property="og:video:type"]', { content: 'text/html' });
I.seeAttributesOnElements('meta[property="og:video:width"]', { content: '1280' });
I.seeAttributesOnElements('meta[property="og:video:height"]', { content: '720' });

I.seeAttributesOnElements('meta[name="twitter:title"]', { content: 'Blocking - JW Showcase' });
I.seeAttributesOnElements('meta[name="twitter:description"]', { content: 'If you\'re brand new to Blender or are getting stuck, check out the Blender 2.8 Fundamentals series.' });
I.seeAttributesOnElements('meta[name="twitter:image"]', { content: 'https://content.jwplatform.com/v2/media/zKT3MFut/poster.jpg?width=720' });
});

Scenario('It renders the correct structured metadata for the series screen', ({ I }) => {
I.amOnPage('http://localhost:8080/s/xdAqW8ya/primitive-animals?e=xdAqW8ya');
I.seeTextEquals('{"@context":"http://schema.org/","@type":"TVEpisode","@id":"http://localhost:8080//s/xdAqW8ya/primitive-animals?e=zKT3MFut","episodeNumber":"1","seasonNumber":"1","name":"Blocking","uploadDate":"2021-03-10T10:00:00.000Z","partOfSeries":{"@type":"TVSeries","@id":"http://localhost:8080//s/xdAqW8ya/primitive-animals","name":"Primitive Animals","numberOfEpisodes":4,"numberOfSeasons":1}}', { css: 'script[type="application/ld+json"]' })
});

0 comments on commit 2d32ecf

Please sign in to comment.