fixed helmet issues and refactored SEO
This commit is contained in:
parent
6fd4a94a6a
commit
0465e97cf0
15 changed files with 201 additions and 65 deletions
|
@ -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',
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -74,7 +74,7 @@ class OpenGraphTags extends React.Component {
|
|||
{/* image twitter tags */}
|
||||
<meta name='twitter:card' content='summary_large_image' />
|
||||
</Helmet>
|
||||
)};
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
24
react/components/SEO/index.jsx
Normal file
24
react/components/SEO/index.jsx
Normal 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;
|
|
@ -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'>
|
||||
|
|
|
@ -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}`} />
|
||||
)}
|
||||
<OpenGraphTags />
|
||||
</Helmet>
|
||||
<SEO pageTitle={pageTitle} canonicalLink={canonicalLink} metaTags={metaTags} />
|
||||
<OpenGraphTags />
|
||||
<div>
|
||||
<AssetDisplay />
|
||||
<Link id='asset-boilerpate' className='link--primary fine-print' to={`/${claimId}/${name}`}>hosted
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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);
|
||||
|
|
24
react/utils/canonicalLink.js
Normal file
24
react/utils/canonicalLink.js
Normal 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
86
react/utils/metaTags.js
Normal 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
8
react/utils/pageTitle.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
const { site: { title: siteTitle } } = require('../../config/speechConfig.js');
|
||||
|
||||
export const createPageTitle = (pageTitle) => {
|
||||
if (!pageTitle) {
|
||||
return `${siteTitle}`;
|
||||
}
|
||||
return `${siteTitle} - ${pageTitle}`;
|
||||
};
|
Loading…
Reference in a new issue