diff --git a/src/components/Page/Page.js b/src/components/Page/Page.js index ed51674f..f53f0aa4 100644 --- a/src/components/Page/Page.js +++ b/src/components/Page/Page.js @@ -21,6 +21,15 @@ const preventDefault = e => { e.preventDefault(); }; +const twitterPageURL = siteTwitterHandle => { + if (siteTwitterHandle && siteTwitterHandle.charAt(0) === '@') { + return `https://twitter.com/${siteTwitterHandle.substring(1)}`; + } else if (siteTwitterHandle) { + return `https://twitter.com/${siteTwitterHandle}`; + } + return null; +}; + class PageComponent extends Component { componentDidMount() { this.historyUnlisten = this.props.history.listen(() => scrollToTop()); @@ -83,6 +92,7 @@ class PageComponent extends Component { const { pathname, search = '' } = history.location; const pathWithSearch = `${pathname}${search}`; + const canonicalRootURL = config.canonicalRootURL; const siteTitle = config.siteTitle; const schemaTitle = intl.formatMessage({ id: 'Page.schemaTitle' }, { siteTitle }); const schemaDescription = intl.formatMessage({ id: 'Page.schemaDescription' }); @@ -91,7 +101,7 @@ class PageComponent extends Component { const facebookImgs = facebookImages || [ { name: 'facebook', - url: `${config.canonicalRootURL}${facebookImage}`, + url: `${canonicalRootURL}${facebookImage}`, width: 1200, height: 630, }, @@ -99,7 +109,7 @@ class PageComponent extends Component { const twitterImgs = twitterImages || [ { name: 'twitter', - url: `${config.canonicalRootURL}${twitterImage}`, + url: `${canonicalRootURL}${twitterImage}`, width: 600, height: 314, }, @@ -123,17 +133,42 @@ class PageComponent extends Component { // eslint-disable-next-line react/no-array-index-key const metaTags = metaToHead.map((metaProps, i) => ); + const facebookPage = config.siteFacebookPage; + const twitterPage = twitterPageURL(config.siteTwitterHandle); + const instagramPage = config.siteInstagramPage; + const sameOrganizationAs = [facebookPage, twitterPage, instagramPage].filter(v => v != null); + // Schema for search engines (helps them to understand what this page is about) // http://schema.org // We are using JSON-LD format - const schemaJSONString = - schema || - JSON.stringify({ + + // Schema attribute can be either single schema object or an array of objects + // This makes it possible to include several different items from the same page. + // E.g. Product, Place, Video + const schemaFromProps = Array.isArray(schema) ? schema : [schema]; + const schemaArrayJSONString = JSON.stringify([ + ...schemaFromProps, + { '@context': 'http://schema.org', - '@type': 'WebPage', + '@type': 'Organization', + '@id': `${canonicalRootURL}#organization`, + url: canonicalRootURL, + name: siteTitle, + sameAs: sameOrganizationAs, + logo: `${canonicalRootURL}/static/webapp-icon-192x192.png`, + address: config.address, + }, + { + '@context': 'http://schema.org', + '@type': 'WebSite', + url: canonicalRootURL, description: schemaDescription, name: schemaTitle, - }); + publisher: { + '@id': `${canonicalRootURL}#organization`, + }, + }, + ]); return (
@@ -147,7 +182,7 @@ class PageComponent extends Component { {metaTags} - + {authInfoError ? (
@@ -165,7 +200,7 @@ class PageComponent extends Component { } } -const { any, arrayOf, bool, func, number, shape, string } = PropTypes; +const { any, array, arrayOf, bool, func, number, object, oneOfType, shape, string } = PropTypes; PageComponent.defaultProps = { className: null, @@ -214,7 +249,7 @@ PageComponent.propTypes = { }) ), published: string, // article:published_time - schema: string, // http://schema.org + schema: oneOfType([object, array]), // http://schema.org tags: string, // article:tag title: string.isRequired, // page title twitterHandle: string, // twitter handle diff --git a/src/config.js b/src/config.js index e6acdfa8..d9be9791 100644 --- a/src/config.js +++ b/src/config.js @@ -187,15 +187,27 @@ const stripeSupportedCountries = [ }, ]; +// Address information is used in SEO schema for Organization (http://schema.org/PostalAddress) +const addressCountry = 'FI'; +const addressRegion = 'Helsinki'; +const postalCode = '00100'; +const streetAddress = 'Bulevardi 14'; + // Canonical root url is needed in social media sharing and SEO optimization purposes. const canonicalRootURL = process.env.REACT_APP_CANONICAL_ROOT_URL || 'http://localhost:3000'; // Site title is needed in meta tags (bots and social media sharing reads those) const siteTitle = 'Saunatime'; -// Twitter handle is needed in meta tags (twitter:site) +// Twitter handle is needed in meta tags (twitter:site). Start it with '@' character const siteTwitterHandle = '@sharetribe'; +// Instagram page is used in SEO schema (http://schema.org/Organization) +const siteInstagramPage = null; + +// Facebook page is used in SEO schema (http://schema.org/Organization) +const siteFacebookPage = null; + // Facebook counts shares with app or page associated by this id // Currently it is unset, but you can read more about fb:app_id from // https://developers.facebook.com/docs/sharing/webmasters#basic @@ -212,7 +224,15 @@ const config = { currencyConfig, stripe: { publishableKey: stripePublishableKey, supportedCountries: stripeSupportedCountries }, canonicalRootURL, + address: { + addressCountry, + addressRegion, + postalCode, + streetAddress, + }, siteTitle, + siteFacebookPage, + siteInstagramPage, siteTwitterHandle, facebookAppId, sentryDsn, diff --git a/src/containers/AuthenticationPage/AuthenticationPage.js b/src/containers/AuthenticationPage/AuthenticationPage.js index 7776c4ee..0ea25b87 100644 --- a/src/containers/AuthenticationPage/AuthenticationPage.js +++ b/src/containers/AuthenticationPage/AuthenticationPage.js @@ -210,13 +210,11 @@ export const AuthenticationPageComponent = props => { logoutError={logoutError} title={schemaTitle} scrollingDisabled={scrollingDisabled} - schema={` - { - "@context": "http://schema.org", - "@type": "WebPage", - "name": "${schemaTitle}", - } - `} + schema={{ + '@context': 'http://schema.org', + '@type': 'WebPage', + name: schemaTitle, + }} >
diff --git a/src/containers/AuthenticationPage/__snapshots__/AuthenticationPage.test.js.snap b/src/containers/AuthenticationPage/__snapshots__/AuthenticationPage.test.js.snap index f61ffa9d..e8a1eff6 100644 --- a/src/containers/AuthenticationPage/__snapshots__/AuthenticationPage.test.js.snap +++ b/src/containers/AuthenticationPage/__snapshots__/AuthenticationPage.test.js.snap @@ -4,13 +4,13 @@ exports[`AuthenticationPageComponent matches snapshot 1`] = ` diff --git a/src/containers/LandingPage/LandingPage.js b/src/containers/LandingPage/LandingPage.js index 7ed5101f..63f7f355 100644 --- a/src/containers/LandingPage/LandingPage.js +++ b/src/containers/LandingPage/LandingPage.js @@ -36,17 +36,13 @@ export const LandingPageComponent = props => { title={schemaTitle} facebookImages={[{ url: facebookImage, width: 1200, height: 630 }]} twitterImages={[{ url: twitterImage, width: 600, height: 314 }]} - schema={` - { - "@context": "http://schema.org", - "@type": "WebPage", - "description": "${schemaDescription}", - "name": "${schemaTitle}", - "image": [ - "${schemaImage}" - ] - } - `} + schema={{ + '@context': 'http://schema.org', + '@type': 'WebPage', + description: schemaDescription, + name: schemaTitle, + image: [schemaImage], + }} >
diff --git a/src/containers/LandingPage/__snapshots__/LandingPage.test.js.snap b/src/containers/LandingPage/__snapshots__/LandingPage.test.js.snap index 734383b0..aa01d486 100644 --- a/src/containers/LandingPage/__snapshots__/LandingPage.test.js.snap +++ b/src/containers/LandingPage/__snapshots__/LandingPage.test.js.snap @@ -15,17 +15,17 @@ exports[`LandingPage matches snapshot 1`] = ` ] } logoutError={null} - schema=" - { - \\"@context\\": \\"http://schema.org\\", - \\"@type\\": \\"WebPage\\", - \\"description\\": \\"LandingPage.schemaDescription\\", - \\"name\\": \\"LandingPage.schemaTitle\\", - \\"image\\": [ - \\"http://localhost:3000/saunatimeFacebook-1200x630.jpg\\" - ] - } - " + schema={ + Object { + "@context": "http://schema.org", + "@type": "WebPage", + "description": "LandingPage.schemaDescription", + "image": Array [ + "http://localhost:3000/saunatimeFacebook-1200x630.jpg", + ], + "name": "LandingPage.schemaTitle", + } + } scrollingDisabled={false} title="LandingPage.schemaTitle" twitterImages={ diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index b241e63b..1340d879 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -335,15 +335,13 @@ export class ListingPageComponent extends Component { facebookImages={facebookImages} twitterImages={twitterImages} canonicalPath={canonicalPath} - schema={` - { - "@context": "http://schema.org", - "@type": "ItemPage", - "description": "${description}", - "name": "${schemaTitle}", - "image": ${schemaImages} - } - `} + schema={{ + '@context': 'http://schema.org', + '@type': 'ItemPage', + description: description, + name: schemaTitle, + image: schemaImages, + }} > {topbar}
diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 90393f34..b5af7fb4 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -9,15 +9,15 @@ exports[`ListingPage matches snapshot 1`] = ` description="listing1 description" facebookImages={Array []} logoutError={null} - schema=" - { - \\"@context\\": \\"http://schema.org\\", - \\"@type\\": \\"ItemPage\\", - \\"description\\": \\"listing1 description\\", - \\"name\\": \\"ListingPage.schemaTitle\\", - \\"image\\": [] - } - " + schema={ + Object { + "@context": "http://schema.org", + "@type": "ItemPage", + "description": "listing1 description", + "image": "[]", + "name": "ListingPage.schemaTitle", + } + } scrollingDisabled={false} title="ListingPage.schemaTitle" twitterImages={Array []} diff --git a/src/containers/SearchPage/SearchPage.js b/src/containers/SearchPage/SearchPage.js index 84ac3ad8..836c7f43 100644 --- a/src/containers/SearchPage/SearchPage.js +++ b/src/containers/SearchPage/SearchPage.js @@ -276,15 +276,13 @@ export class SearchPageComponent extends Component { scrollingDisabled={scrollingDisabled} description={schemaDescription} title={schemaTitle} - schema={` - { - "@context": "http://schema.org", - "@type": "SearchResultsPage", - "description": "${schemaDescription}", - "name": "${schemaTitle}", - "mainEntity": [${schemaMainEntity}] - } - `} + schema={{ + '@context': 'http://schema.org', + '@type': 'SearchResultsPage', + description: schemaDescription, + name: schemaTitle, + mainEntity: [schemaMainEntity], + }} >
diff --git a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap index 5d94ec4a..27559636 100644 --- a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap +++ b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap @@ -5,15 +5,17 @@ exports[`SearchPageComponent matches snapshot 1`] = ` authInfoError={null} description="SearchPage.schemaDescription" logoutError={null} - schema=" - { - \\"@context\\": \\"http://schema.org\\", - \\"@type\\": \\"SearchResultsPage\\", - \\"description\\": \\"SearchPage.schemaDescription\\", - \\"name\\": \\"SearchPage.schemaTitle\\", - \\"mainEntity\\": [{\\"@type\\":\\"ItemList\\",\\"name\\":\\"SearchPage.schemaMapSearch\\",\\"itemListOrder\\":\\"http://schema.org/ItemListOrderAscending\\",\\"itemListElement\\":[]}] - } - " + schema={ + Object { + "@context": "http://schema.org", + "@type": "SearchResultsPage", + "description": "SearchPage.schemaDescription", + "mainEntity": Array [ + "{\\"@type\\":\\"ItemList\\",\\"name\\":\\"SearchPage.schemaMapSearch\\",\\"itemListOrder\\":\\"http://schema.org/ItemListOrderAscending\\",\\"itemListElement\\":[]}", + ], + "name": "SearchPage.schemaTitle", + } + } scrollingDisabled={false} title="SearchPage.schemaTitle" >