fixed helmet issues and refactored SEO

This commit is contained in:
bill bittner 2018-02-23 11:00:46 -08:00
parent 6fd4a94a6a
commit 0465e97cf0
15 changed files with 201 additions and 65 deletions

View file

@ -26,6 +26,7 @@ module.exports = {
title: 'Spee.ch',
name : 'Spee.ch',
host : 'https://spee.ch',
description: 'Open-source, decentralized image and video sharing.'
},
claim: {
defaultTitle : 'Spee.ch',

View file

@ -7,6 +7,7 @@ import { StaticRouter } from 'react-router-dom';
import GAListener from '../react/components/GAListener';
import App from '../react/app';
import renderFullPage from './renderFullPage.js';
import Helmet from 'react-helmet';
module.exports = (req, res) => {
let context = {};
@ -25,6 +26,9 @@ module.exports = (req, res) => {
</Provider>
);
// get head tags from helmet
const helmet = Helmet.renderStatic();
// check for a redirect
if (context.url) {
// Somewhere a `<Redirect>` was rendered
@ -37,5 +41,5 @@ module.exports = (req, res) => {
const preloadedState = store.getState();
// send the rendered page back to the client
res.send(renderFullPage(html, preloadedState));
res.send(renderFullPage(helmet, html, preloadedState));
};

View file

@ -11,6 +11,6 @@ export function getChannelData (name, id) {
export function getChannelClaims (name, longId, page) {
console.log('getting channel claims for channel:', name, longId);
if (!page) page = 1;
const url = `/api/channel/claims/${name}/${longId}/${page}`;
const url = `${host}/api/channel/claims/${name}/${longId}/${page}`;
return Request(url);
};

View file

@ -1,17 +1,18 @@
import React from 'react';
import NavBar from 'containers/NavBar';
import Helmet from 'react-helmet';
const { site: { title, host } } = require('../../../config/speechConfig.js');
import SEO from 'components/SEO';
import { createPageTitle } from 'utils/pageTitle';
import { createBasicCanonicalLink } from 'utils/canonicalLink';
import { createBasicMetaTags } from 'utils/metaTags';
class AboutPage extends React.Component {
render () {
const pageTitle = createPageTitle('About');
const canonicalLink = createBasicCanonicalLink('about');
const metaTags = createBasicMetaTags();
return (
<div>
<Helmet>
<title>{title} - About</title>
<link rel='canonical' href={`${host}/about`} />
</Helmet>
<SEO pageTitle={pageTitle} canonicalLink={canonicalLink} metaTags={metaTags} />
<NavBar />
<div className='row row--padded'>
<div className='column column--5 column--med-10 align-content-top'>

View file

@ -1,18 +1,19 @@
import React from 'react';
import Helmet from 'react-helmet';
import SEO from 'components/SEO';
import NavBar from 'containers/NavBar';
import PublishTool from 'containers/PublishTool';
const { site: { title, host } } = require('../../../config/speechConfig.js');
import { createPageTitle } from 'utils/pageTitle';
import { createBasicCanonicalLink } from 'utils/canonicalLink';
import { createBasicMetaTags } from 'utils/metaTags';
class HomePage extends React.Component {
render () {
const pageTitle = createPageTitle();
const canonicalLink = createBasicCanonicalLink();
const metaTags = createBasicMetaTags();
return (
<div className={'row row--tall flex-container--column'}>
<Helmet>
<title>{title}</title>
<link rel='canonical' href={`${host}/`} />
</Helmet>
<SEO pageTitle={pageTitle} canonicalLink={canonicalLink} metaTags={metaTags} />
<NavBar />
<div className={'row row--tall row--padded flex-container--column'}>
<PublishTool />

View file

@ -74,7 +74,7 @@ class OpenGraphTags extends React.Component {
{/* image twitter tags */}
<meta name='twitter:card' content='summary_large_image' />
</Helmet>
)};
)}
</div>
);
}

View file

@ -0,0 +1,24 @@
import React from 'react';
import Helmet from 'react-helmet';
import PropTypes from 'prop-types';
class SEO extends React.Component {
render () {
const { pageTitle, metaTags, canonicalLink } = this.props;
return (
<Helmet
title={pageTitle}
link={[{rel: 'canonical', href: canonicalLink}]}
meta={metaTags}
/>
);
}
};
SEO.propTypes = {
pageTitle : PropTypes.string.isRequired,
metaTags : PropTypes.array.isRequired,
canonicalLink: PropTypes.string.isRequired,
};
export default SEO;

View file

@ -1,33 +1,25 @@
import React from 'react';
import Helmet from 'react-helmet';
import OpenGraphTags from 'components/OpenGraphTags';
import SEO from 'components/SEO';
import NavBar from 'containers/NavBar';
import ErrorPage from 'components/ErrorPage';
import AssetTitle from 'components/AssetTitle';
import AssetDisplay from 'components/AssetDisplay';
import AssetInfo from 'components/AssetInfo';
const { site: { title, host } } = require('../../../config/speechConfig.js');
import { createPageTitle } from 'utils/pageTitle';
import { createAssetCanonicalLink } from 'utils/canonicalLink';
import { createAssetMetaTags } from 'utils/metaTags';
class ShowAssetDetails extends React.Component {
render () {
const { asset } = this.props;
if (asset) {
let channelName, certificateId, name, claimId;
if (asset.claimData) {
({ channelName, certificateId, name, claimId } = asset.claimData);
};
const { name } = asset.claimData;
const pageTitle = createPageTitle(`${name} - details`);
const canonicalLink = createAssetCanonicalLink(asset);
const metaTags = createAssetMetaTags(asset);
return (
<div>
<Helmet>
<title>{title} - {name} - details</title>
{channelName ? (
<link rel='canonical' href={`${host}/${channelName}:${certificateId}/${name}`} />
) : (
<link rel='canonical' href={`${host}/${claimId}/${name}`} />
)}
</Helmet>
<OpenGraphTags />
<SEO pageTitle={pageTitle} canonicalLink={canonicalLink} metaTags={metaTags} />
<NavBar />
<div className='row row--tall row--padded'>
<div className='column column--10'>

View file

@ -1,30 +1,24 @@
import React from 'react';
import Helmet from 'react-helmet';
import SEO from 'components/SEO';
import OpenGraphTags from 'components/OpenGraphTags';
import { Link } from 'react-router-dom';
import AssetDisplay from 'components/AssetDisplay';
const { site: { title, host } } = require('../../../config/speechConfig.js');
import { createPageTitle } from 'utils/pageTitle';
import { createAssetCanonicalLink } from 'utils/canonicalLink';
import { createAssetMetaTags } from 'utils/metaTags';
class ShowLite extends React.Component {
render () {
const { asset } = this.props;
if (asset) {
let channelName, certificateId, name, claimId, fileExt;
if (asset.claimData) {
({ channelName, certificateId, name, claimId, fileExt } = asset.claimData);
};
const { name, claimId } = asset.claimData;
const pageTitle = createPageTitle(name);
const canonicalLink = createAssetCanonicalLink(asset);
const metaTags = createAssetMetaTags(asset);
return (
<div className='row row--tall flex-container--column flex-container--center-center'>
<Helmet>
<title>{title} - {name}</title>
{channelName ? (
<link rel='canonical' href={`${host}/${channelName}:${certificateId}/${name}.${fileExt}`} />
) : (
<link rel='canonical' href={`${host}/${claimId}/${name}.${fileExt}`} />
)}
<SEO pageTitle={pageTitle} canonicalLink={canonicalLink} metaTags={metaTags} />
<OpenGraphTags />
</Helmet>
<div>
<AssetDisplay />
<Link id='asset-boilerpate' className='link--primary fine-print' to={`/${claimId}/${name}`}>hosted

View file

@ -1,22 +1,23 @@
import React from 'react';
import Helmet from 'react-helmet';
import SEO from 'components/SEO';
import ErrorPage from 'components/ErrorPage';
import NavBar from 'containers/NavBar';
import ChannelClaimsDisplay from 'containers/ChannelClaimsDisplay';
const { site: { title, host } } = require('../../../config/speechConfig.js');
import { createPageTitle } from 'utils/pageTitle';
import { createChannelCanonicalLink } from 'utils/canonicalLink';
import { createChannelMetaTags } from 'utils/metaTags';
class ShowChannel extends React.Component {
render () {
const { channel } = this.props;
if (channel) {
const { name, longId, shortId } = channel;
const pageTitle = createPageTitle(`${name}`);
const canonicalLink = createChannelCanonicalLink(channel);
const metaTags = createChannelMetaTags(channel);
return (
<div>
<Helmet>
<title>{title} - {name}</title>
<link rel='canonical' href={`${host}/${name}:${longId}`} />
</Helmet>
<SEO pageTitle={pageTitle} canonicalLink={canonicalLink} metaTags={metaTags} />
<NavBar />
<div className='row row--tall row--padded'>
<div className='column column--10'>

View file

@ -1,11 +1,12 @@
import React from 'react';
import { withRouter } from 'react-router-dom';
import Helmet from 'react-helmet';
import SEO from 'components/SEO';
import NavBar from 'containers/NavBar';
import ChannelLoginForm from 'containers/ChannelLoginForm';
import ChannelCreateForm from 'containers/ChannelCreateForm';
const { site: { title, host } } = require('../../../config/speechConfig.js');
import { createPageTitle } from 'utils/pageTitle';
import { createBasicCanonicalLink } from 'utils/canonicalLink';
import { createBasicMetaTags } from 'utils/metaTags';
class LoginPage extends React.Component {
componentWillReceiveProps (newProps) {
@ -16,12 +17,12 @@ class LoginPage extends React.Component {
}
}
render () {
const pageTitle = createPageTitle('Login');
const canonicalLink = createBasicCanonicalLink('login');
const metaTags = createBasicMetaTags();
return (
<div>
<Helmet>
<title>{title} - Login</title>
<link rel='canonical' href={`${host}/login`} />
</Helmet>
<SEO pageTitle={pageTitle} canonicalLink={canonicalLink} metaTags={metaTags} />
<NavBar />
<div className='row row--padded'>
<div className='column column--5 column--med-10 align-content-top'>

View file

@ -50,7 +50,6 @@ function * parseAndUpdateClaimOnly (claim) {
export function * handleShowPageUri (action) {
console.log('handleShowPageUri');
console.log('action:', action);
const { identifier, claim } = action.data;
if (identifier) {
return yield call(parseAndUpdateIdentifierAndClaim, identifier, claim);

View file

@ -0,0 +1,24 @@
const { site: { host } } = require('../../config/speechConfig.js');
export const createBasicCanonicalLink = (page) => {
if (!page) {
return `${host}`;
};
return `${host}/${page}`;
};
export const createAssetCanonicalLink = (asset) => {
let channelName, certificateId, name, claimId;
if (asset.claimData) {
({ channelName, certificateId, name, claimId } = asset.claimData);
};
if (channelName) {
return `${host}/${channelName}:${certificateId}/${name}`;
};
return `${host}/${claimId}/${name}`;
};
export const createChannelCanonicalLink = (channel) => {
const { name, longId } = channel;
return `${host}/${name}:${longId}`;
};

86
react/utils/metaTags.js Normal file
View file

@ -0,0 +1,86 @@
const { site: { title, host, description }, claim: { defaultThumbnail, defaultDescription } } = require('../../config/speechConfig.js');
const determineOgThumbnailContentType = (thumbnail) => {
if (thumbnail) {
const fileExt = thumbnail.substring(thumbnail.lastIndexOf('.'));
switch (fileExt) {
case 'jpeg':
case 'jpg':
return 'image/jpeg';
case 'png':
return 'image/png';
case 'gif':
return 'image/gif';
case 'mp4':
return 'video/mp4';
default:
return 'image/jpeg';
}
}
return '';
};
export const createBasicMetaTags = () => {
return [
{property: 'og:title', content: title},
{property: 'og:url', content: host},
{property: 'og:site_name', content: title},
{property: 'og:description', content: description},
{property: 'twitter:site', content: '@spee_ch'},
{property: 'twitter:card', content: 'summary'},
];
};
export const createChannelMetaTags = (channel) => {
const { name, longId } = channel;
return [
{property: 'og:title', content: `${name} on ${title}`},
{property: 'og:url', content: `${host}/${name}:${longId}`},
{property: 'og:site_name', content: title},
{property: 'og:description', content: `${name}, a channel on ${title}`},
{property: 'twitter:site', content: '@spee_ch'},
{property: 'twitter:card', content: 'summary'},
];
};
export const createAssetMetaTags = (asset) => {
const { claimData } = asset;
const { contentType } = claimData;
const embedUrl = `${host}/${claimData.claimId}/${claimData.name}`;
const showUrl = `${host}/${claimData.claimId}/${claimData.name}`;
const source = `${host}/${claimData.claimId}/${claimData.name}.${claimData.fileExt}`;
const ogTitle = claimData.title || claimData.name;
const ogDescription = claimData.description || defaultDescription;
const ogThumbnailContentType = determineOgThumbnailContentType(claimData.thumbnail);
const ogThumbnail = claimData.thumbnail || defaultThumbnail;
const metaTags = [
{property: 'og:title', content: ogTitle},
{property: 'og:url', content: showUrl},
{property: 'og:site_name', content: title},
{property: 'og:description', content: ogDescription},
{property: 'og:image:width', content: 600},
{property: 'og:image:height', content: 315},
{property: 'twitter:site', content: '@spee_ch'},
];
if (contentType === 'video/mp4' || contentType === 'video/webm') {
metaTags.push({property: 'og:video', content: source});
metaTags.push({property: 'og:video:secure_url', content: source});
metaTags.push({property: 'og:video:type', content: contentType});
metaTags.push({property: 'og:image', content: ogThumbnail});
metaTags.push({property: 'og:image:type', content: ogThumbnailContentType});
metaTags.push({property: 'og:type', content: 'video'});
metaTags.push({property: 'twitter:card', content: 'player'});
metaTags.push({property: 'twitter:player', content: embedUrl});
metaTags.push({property: 'twitter:player:width', content: 600});
metaTags.push({property: 'twitter:text:player_width', content: 600});
metaTags.push({property: 'twitter:player:height', content: 337});
metaTags.push({property: 'twitter:player:stream', content: source});
metaTags.push({property: 'twitter:player:stream:content_type', content: contentType});
} else {
metaTags.push({property: 'og:image', content: source});
metaTags.push({property: 'og:image:type', content: contentType});
metaTags.push({property: 'og:type', content: 'article'});
metaTags.push({property: 'twitter:card', content: 'summary_large_image'});
}
return metaTags;
};

8
react/utils/pageTitle.js Normal file
View file

@ -0,0 +1,8 @@
const { site: { title: siteTitle } } = require('../../config/speechConfig.js');
export const createPageTitle = (pageTitle) => {
if (!pageTitle) {
return `${siteTitle}`;
}
return `${siteTitle} - ${pageTitle}`;
};