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', title: 'Spee.ch',
name : 'Spee.ch', name : 'Spee.ch',
host : 'https://spee.ch', host : 'https://spee.ch',
description: 'Open-source, decentralized image and video sharing.'
}, },
claim: { claim: {
defaultTitle : 'Spee.ch', defaultTitle : 'Spee.ch',

View file

@ -7,6 +7,7 @@ import { StaticRouter } from 'react-router-dom';
import GAListener from '../react/components/GAListener'; import GAListener from '../react/components/GAListener';
import App from '../react/app'; import App from '../react/app';
import renderFullPage from './renderFullPage.js'; import renderFullPage from './renderFullPage.js';
import Helmet from 'react-helmet';
module.exports = (req, res) => { module.exports = (req, res) => {
let context = {}; let context = {};
@ -25,6 +26,9 @@ module.exports = (req, res) => {
</Provider> </Provider>
); );
// get head tags from helmet
const helmet = Helmet.renderStatic();
// check for a redirect // check for a redirect
if (context.url) { if (context.url) {
// Somewhere a `<Redirect>` was rendered // Somewhere a `<Redirect>` was rendered
@ -37,5 +41,5 @@ module.exports = (req, res) => {
const preloadedState = store.getState(); const preloadedState = store.getState();
// send the rendered page back to the client // 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) { export function getChannelClaims (name, longId, page) {
console.log('getting channel claims for channel:', name, longId); console.log('getting channel claims for channel:', name, longId);
if (!page) page = 1; if (!page) page = 1;
const url = `/api/channel/claims/${name}/${longId}/${page}`; const url = `${host}/api/channel/claims/${name}/${longId}/${page}`;
return Request(url); return Request(url);
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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