diff --git a/gatsby-config.js b/gatsby-config.js
index 89f6f96..8a3c1de 100644
--- a/gatsby-config.js
+++ b/gatsby-config.js
@@ -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`,
diff --git a/gatsby-node.js b/gatsby-node.js
index 84800ec..57b0667 100644
--- a/gatsby-node.js
+++ b/gatsby-node.js
@@ -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
diff --git a/src/pages/404.js b/src/pages/404.js
index d0e63f3..9a18cee 100644
--- a/src/pages/404.js
+++ b/src/pages/404.js
@@ -1,4 +1,5 @@
import * as React from "react"
+import Seo from "../sitecomponents/SEO"
const NotFoundPage = ({ location }) => {
return (
@@ -9,6 +10,6 @@ const NotFoundPage = ({ location }) => {
)
}
-export const Head = () =>
404: Not Found
+export const Head = () =>
export default NotFoundPage
\ No newline at end of file
diff --git a/src/pages/discussion-leaderboard.js b/src/pages/discussion-leaderboard.js
index 3942796..13e427d 100644
--- a/src/pages/discussion-leaderboard.js
+++ b/src/pages/discussion-leaderboard.js
@@ -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();
@@ -38,11 +39,6 @@ const LeaderBoard = () => {
theme === 'light' ? layer5LeaderboardLightMode : layer5LeaderboardDarkMode;
return (
<>
- Layer5 LeaderBoard
-
{
};
export default LeaderBoard;
+
+export const Head = () => (
+
+);
diff --git a/src/pages/index.js b/src/pages/index.js
index 22a6248..8685f30 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -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';
@@ -218,11 +219,6 @@ const App = () => {
// };
return (
<>
- Layer5 Recognition Program
-
@@ -380,3 +376,5 @@ const App = () => {
};
export default App;
+
+export const Head = () => ;
diff --git a/src/sitecomponents/SEO/index.js b/src/sitecomponents/SEO/index.js
new file mode 100644
index 0000000..b4ad8c0
--- /dev/null
+++ b/src/sitecomponents/SEO/index.js
@@ -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 = () => ;
+ */
+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 (
+ <>
+ {seo.title}
+
+ {seo.url && }
+
+
+
+
+
+ {seo.url && }
+
+
+ {!image && (
+
+ )}
+ {!image && (
+
+ )}
+
+
+
+
+
+
+ {seo.twitter && }
+ {seo.twitter && }
+
+ {children}
+ >
+ );
+};
+
+export default Seo;