Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ module.exports = {
title: `Layer5 Recognition Program`,
description: `Showcasing Your Achievements as a User and a Contributor`,
siteUrl: `https://badges.layer5.io`,
social: {
twitter: `@layer5`,
},
},
plugins: [
`gatsby-plugin-styled-components`,
Expand Down
2 changes: 2 additions & 0 deletions gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ exports.createSchemaCustomization = ({ actions }) => {
// blog posts are stored inside "content/blog" instead of returning an error
createTypes(`
type SiteSiteMetadata {
title: String
description: String
author: Author
siteUrl: String
social: Social
Expand Down
3 changes: 2 additions & 1 deletion src/pages/404.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from "react"
import Seo from "../sitecomponents/SEO"

const NotFoundPage = ({ location }) => {
return (
Expand All @@ -9,6 +10,6 @@ const NotFoundPage = ({ location }) => {
)
}

export const Head = () => <title>404: Not Found</title>
export const Head = () => <Seo title="404: Not Found" />

export default NotFoundPage
10 changes: 5 additions & 5 deletions src/pages/discussion-leaderboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '../sitecomponents/index.style';
import Navigation from '../sitecomponents/Navigation';
import Header from '../sitecomponents/Leaderboard/Header';
import Seo from '../sitecomponents/SEO';

const LeaderBoard = () => {
const [theme, toggleTheme] = useDarkMode();
Expand All @@ -38,11 +39,6 @@ const LeaderBoard = () => {
theme === 'light' ? layer5LeaderboardLightMode : layer5LeaderboardDarkMode;
return (
<>
<title>Layer5 LeaderBoard</title>
<meta
name="description"
content="Showcasing Your Achievements as a User and a Contributor"
/>
<ThemeProvider theme={themeMode}>
<GlobalStyle />
<Navigation
Expand All @@ -66,3 +62,7 @@ const LeaderBoard = () => {
};

export default LeaderBoard;

export const Head = () => (
<Seo title="Layer5 Leaderboard" pathname="/discussion-leaderboard" />
);
8 changes: 3 additions & 5 deletions src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import recognitionLogo from '../assets/images/recognition-program.png';
import recognitionBanner from '../assets/images/recognition-banner.png';
import '../fonts.css';
import GithubLogo from './githubLogo';
import Seo from '../sitecomponents/SEO';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import layer5Logo from '../assets/images/layer5/layer5-badges.png';
import layer5LogoLight from '../assets/images/layer5/layer5-badges-white.png';
Expand Down Expand Up @@ -218,11 +219,6 @@ const App = () => {
// };
return (
<>
<title>Layer5 Recognition Program</title>
<meta
name="description"
content="Showcasing Your Achievements as a User and a Contributor"
/>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={themeMode}>
<GlobalStyle />
Expand Down Expand Up @@ -380,3 +376,5 @@ const App = () => {
};

export default App;

export const Head = () => <Seo pathname="/" />;
104 changes: 104 additions & 0 deletions src/sitecomponents/SEO/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { useStaticQuery, graphql, withPrefix } from 'gatsby';
import defaultSocialImage from '../../assets/images/recognition-banner.png';

// Intrinsic dimensions of `defaultSocialImage`. Update alongside the image so
// that crawlers can lay the card out before the image itself is fetched.
const DEFAULT_SOCIAL_IMAGE_WIDTH = 3629;
const DEFAULT_SOCIAL_IMAGE_HEIGHT = 1599;

/**
* Renders the document's SEO metadata, including Open Graph and Twitter Card
* tags so that links to the site unfurl with a rich preview on social media,
* Slack, Discord and other community channels.
*
* Intended to be used from a page's `Head` export:
*
* export const Head = () => <Seo pathname="/" />;
*/
const Seo = ({ title, description, image, pathname, children }) => {
const { site } = useStaticQuery(graphql`
query SeoMetadata {
site {
siteMetadata {
title
description
siteUrl
social {
twitter
}
}
}
}
`);

const metadata = site.siteMetadata;
const siteUrl = metadata.siteUrl.replace(/\/$/, '');
// Gatsby serves pages with a trailing slash, so keep the canonical and
// `og:url` values in step with the production URL that is actually shared.
// A page with no URL of its own omits `pathname` and gets neither tag: the
// 404 is served for every unknown path, so claiming `/404/` as its canonical
// would be a claim about a URL the visitor did not request.
const path = pathname ? pathname.replace(/\/?$/, '/') : null;

// Assets do need the prefix. An imported asset gets it from webpack's
// publicPath, so `defaultSocialImage` is already prefixed and must not be
// passed through `withPrefix` again; a caller-supplied path out of
// `static/` does not, so it goes through `withPrefix`.
const imageSrc = image ? withPrefix(image) : defaultSocialImage;

const seo = {
title: title || metadata.title,
description: description || metadata.description,
// Deliberately unprefixed. `pathPrefix` is only ever set by the PR preview
// workflow, which is an ephemeral deployment; production is served from the
// root of siteUrl. The canonical and og:url of a preview should therefore
// point at the production route, not at the preview path.
url: path && `${siteUrl}${path}`,
// Only a root-relative path is ours to qualify. `withPrefix` returns an
// absolute URL unchanged, and webpack inlines a small enough asset as a
// `data:` URI, so prepending siteUrl unconditionally would corrupt both.
image: imageSrc.startsWith('/') ? `${siteUrl}${imageSrc}` : imageSrc,
twitter: metadata.social?.twitter,
};

return (
<>
<title>{seo.title}</title>
<meta name="description" content={seo.description} />
{seo.url && <link rel="canonical" href={seo.url} />}

<meta property="og:type" content="website" />
<meta property="og:site_name" content={metadata.title} />
<meta property="og:title" content={seo.title} />
<meta property="og:description" content={seo.description} />
{seo.url && <meta property="og:url" content={seo.url} />}
<meta property="og:image" content={seo.image} />
<meta property="og:image:alt" content={seo.title} />
{!image && (
<meta
property="og:image:width"
content={DEFAULT_SOCIAL_IMAGE_WIDTH}
/>
)}
{!image && (
<meta
property="og:image:height"
content={DEFAULT_SOCIAL_IMAGE_HEIGHT}
/>
)}

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={seo.title} />
<meta name="twitter:description" content={seo.description} />
<meta name="twitter:image" content={seo.image} />
<meta name="twitter:image:alt" content={seo.title} />
{seo.twitter && <meta name="twitter:site" content={seo.twitter} />}
{seo.twitter && <meta name="twitter:creator" content={seo.twitter} />}

{children}
</>
);
};

export default Seo;
Loading