diff --git a/docs/docs/glossary/server-side-rendering.md b/docs/docs/glossary/server-side-rendering.md index abd479032702b..7fd222c10d7a5 100644 --- a/docs/docs/glossary/server-side-rendering.md +++ b/docs/docs/glossary/server-side-rendering.md @@ -44,6 +44,6 @@ Instead of purely server-side rendering, Gatsby uses the same APIs to create sta - [Why server-side render?](/blog/2019-04-02-behind-the-scenes-what-makes-gatsby-great/#why-server-side-render) from _Behind the Scenes: What makes Gatsby Great_ -- [Search Engine Optimization (SEO) and Social Sharing Cards with Gatsby](/tutorial/seo-and-social-sharing-cards-tutorial/#reach-skip-nav) +- [Adding an SEO Component](/docs/how-to/adding-common-features/adding-seo-component/) - [What is a Static Site Generator?](/docs/glossary/static-site-generator/#what-is-a-static-site-generator) from the Gatsby docs diff --git a/docs/docs/tutorial/part-2/index.mdx b/docs/docs/tutorial/part-2/index.mdx index bcd2efd878fb2..bdd5fcbb43cdb 100644 --- a/docs/docs/tutorial/part-2/index.mdx +++ b/docs/docs/tutorial/part-2/index.mdx @@ -15,6 +15,7 @@ To build out the basic page structure for your blog site, you'll need to know ab By the end of this part of the Tutorial, you will be able to: * Create **page components** to add new pages to your site. +* Add a title to your site using the **Gatsby Head API** * Import and use a **pre-built component** from another package. * Create your own **reusable "building block" component**. * Use component **props** to change the way a component renders. @@ -24,7 +25,7 @@ By the end of this part of the Tutorial, you will be able to: **Prefer a video?** -If you'd rather follow along with a video, here's a recording of a livestream that covers all the material for Part 2. +If you'd rather follow along with a video, here's a recording of a livestream that covers all the material for Part 2. **Please note:** At the time of recording the Gatsby Head API didn't exist yet and thus the video contents and text contents are different. Please always follow the written instructions. We'll do our best to record a new version in the future, thanks for understanding! Don't want to miss any future livestreams? Follow our [Gatsby Twitch channel](https://www.twitch.tv/gatsbyjs). @@ -183,13 +184,15 @@ import * as React from 'react' const IndexPage = () => { return (
- Home Page

Welcome to my Gatsby site!

I'm making this by following the Gatsby Tutorial.

) } +// You'll learn about this in the next task, just copy it for now +export const Head = () => Home Page + // Step 3: Export your component export default IndexPage ``` @@ -222,7 +225,6 @@ import * as React from 'react' const AboutPage = () => { return (
- About Me

About Me

Hi there! I'm the proud creator of this site, which I built with Gatsby.

@@ -233,9 +235,52 @@ const AboutPage = () => { export default AboutPage ``` -2. In a web browser, visit `localhost:8000/about`. When your development server finishes rebuilding your site, the About page should look something like this: +2. Add a page title to your page. Gatsby lets you define a `` and other [document metadata](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) with the [Gatsby Head API](/docs/reference/built-in-components/gatsby-head/). You have to export a component called `Head` from your page template to apply the metadata. +Adding such metadata helps search engines like Google to better understand your site. For this tutorial you'll only be adding titles to pages but you can also later add other metadata. + +```js:title=src/pages/about.js +import * as React from 'react' + +const AboutPage = () => { + return ( + <main> + <h1>About Me</h1> + <p>Hi there! I'm the proud creator of this site, which I built with Gatsby.</p> + </main> + ) +} -![A screenshot of "localhost:8000/about" in a web browser. It has a heading that says, "About Me", and a paragraph that says, "Hi there! I'm the proud creator of this site, which I built with Gatsby."](./about-page.png) +// highlight-next-line +export const Head = () => <title>About Me<title/> + +export default AboutPage +``` +<Announcement style={{marginBottom: "1.5rem"}}> + +**Key Gatsby Concept** 💡 + +You can use the [Gatsby Head API](/docs/reference/built-in-components/gatsby-head/) by exporting a named function called `Head` in your pages and page templates (e.g. the ones used by `createPage` or the File System Route API). + +Be sure to capitalize `Head` and please note that exporting this named function inside a component like `Layout` won't add the metadata to the `<head>`. The above works because you're exporting `Head` in a page inside `src/pages`. + +You can add any valid `<head>` tags inside the function and they'll be added to the page, for example: + +```js +export const Head = () => ( + <> + <title>About Me<title/> + <meta name="description" content="Your description" /> + </> +) +``` + +After going through this tutorial, be sure to check out [Adding an SEO Component](/docs/how-to/adding-common-features/adding-seo-component/). + +</Announcement> + +3. In a web browser, visit `localhost:8000/about`. When your development server finishes rebuilding your site, the About page should look something like this: + +![A screenshot of "localhost:8000/about" in a web browser. It has a heading and page title that says, "About Me", and a paragraph that says, "Hi there! I'm the proud creator of this site, which I built with Gatsby."](./about-page.png) <Announcement style={{marginBottom: "1.5rem"}}> @@ -343,7 +388,6 @@ import { Link } from 'gatsby' const IndexPage = () => { return ( <main> - <title>Home Page

Welcome to my Gatsby site!

{/* highlight-next-line */} About @@ -352,6 +396,8 @@ const IndexPage = () => { ) } +export const Head = () => Home Page + export default IndexPage ``` @@ -365,7 +411,6 @@ import { Link } from 'gatsby' // highlight-line const AboutPage = () => { return (
- About Me

About Me

{/* highlight-next-line */} Back to Home @@ -374,6 +419,8 @@ const AboutPage = () => { ) } +export const Head = () => About Me + export default AboutPage ``` @@ -469,7 +516,7 @@ In the browser, the actual DOM elements will look something like this: Follow the steps below to create a `Layout` component and add it to your Home and About pages. -1. Create a new file called `src/components/layout.js`. Insert the following code to define your `Layout` component. This component will render a dynamic page title and heading (from the `pageTitle` prop), a list of navigation links, and the contents passed in with the `children` prop. To improve accessibility, there's also a `
` element wrapping the page-specific elements (the `

` heading and the contents from `children`). +1. Create a new file called `src/components/layout.js`. Insert the following code to define your `Layout` component. This component will render a dynamic heading (from the `pageTitle` prop), a list of navigation links, and the contents passed in with the `children` prop. To improve accessibility, there's also a `
` element wrapping the page-specific elements (the `

` heading and the contents from `children`). ```js:title=src/components/layout.js import * as React from 'react' @@ -478,7 +525,6 @@ import { Link } from 'gatsby' const Layout = ({ pageTitle, children }) => { return (
- {pageTitle}

} diff --git a/docs/docs/tutorial/part-5/index.mdx b/docs/docs/tutorial/part-5/index.mdx index 05d4942234d47..ad28c5818d6a2 100644 --- a/docs/docs/tutorial/part-5/index.mdx +++ b/docs/docs/tutorial/part-5/index.mdx @@ -419,6 +419,7 @@ Now that your GraphQL query is all set up, it's time to replace the page query i import * as React from 'react' import { graphql } from 'gatsby' import Layout from '../components/layout' + import Seo from '../../components/seo' const BlogPage = ({ data }) => { return ( @@ -443,6 +444,8 @@ Now that your GraphQL query is all set up, it's time to replace the page query i ` // highlight-end + export const Head = () => + export default BlogPage ``` @@ -484,6 +487,8 @@ Now that your GraphQL query is all set up, it's time to replace the page query i } ` + export const Head = () => + export default BlogPage ``` @@ -497,6 +502,7 @@ Now that your GraphQL query is all set up, it's time to replace the page query i import * as React from 'react' import { graphql } from 'gatsby' import Layout from '../components/layout' +import Seo from '../../components/seo' const BlogPage = ({ data }) => { return ( @@ -531,6 +537,8 @@ export const query = graphql` } ` +export const Head = () => + export default BlogPage ``` diff --git a/docs/docs/tutorial/part-6/index.mdx b/docs/docs/tutorial/part-6/index.mdx index a262a6dbeddd5..baa99e9498a7b 100644 --- a/docs/docs/tutorial/part-6/index.mdx +++ b/docs/docs/tutorial/part-6/index.mdx @@ -51,7 +51,7 @@ To create a collection route: 1. Decide what type of node you want to create pages from. 2. Choose which field on that node to use in the route (the URL) for your pages. 3. Create a new page component in your `src/pages` directory using the following naming convention: `{nodeType.field}.js`. - * Don't forget to include the curly braces (`{}`) in your filenames to indicate the dynamic part of the route! + * Don't forget to include the curly braces (`{}`) in your filenames to indicate the dynamic part of the route! For example, if you wanted to create a separate page for each `Product` node, and you wanted to use the product's `name` field in the URL, you'd create a new file at `src/pages/{Product.name}.js`. Then Gatsby would create those pages at routes like `/water-bottle` or `/sweatshirt` or `/notebook`. @@ -146,6 +146,7 @@ When you build your site, Gatsby looks at the page components in your `src/pages ```js:title=src/pages/{mdx.frontmatter__slug}.js import * as React from 'react' import Layout from '../components/layout' + import Seo from '../../components/seo' const BlogPost = () => { return ( @@ -155,6 +156,8 @@ When you build your site, Gatsby looks at the page components in your `src/pages ) } + export const Head = () => + export default BlogPost ``` @@ -330,7 +333,7 @@ Under the hood, Gatsby makes both of these values available to use as query vari ```js Object { // ... - pageContext: Object { + pageContext: Object { id: "11b3a825-30c5-551d-a713-dd748e7d554a" frontmatter__slug: "my-first-post" } @@ -377,6 +380,7 @@ The JSON object in the Query Variables section should look something like the on import * as React from 'react' import { graphql } from 'gatsby' // highlight-line import Layout from '../../components/layout' + import Seo from '../../components/seo' const BlogPost = () => { return ( @@ -399,14 +403,17 @@ The JSON object in the Query Variables section should look something like the on ` // highlight-end + export const Head = () => + export default BlogPost ``` -3. In [Part 4](/docs/tutorial/part-4/), you learned that Gatsby passes in the results from your page query into your page component as a `data` prop. You can update your `BlogPost` component to use the `data` prop and render the contents of your blog post. The actual MDX content, ready to render, will be passed as a `children` prop to the page component. +3. In [Part 4](/docs/tutorial/part-4/), you learned that Gatsby passes in the results from your page query into your page component as a `data` prop. It also passes `data` to the Gatsby Head API. You can update your `BlogPost` component to use the `data` prop and render the contents of your blog post. The actual MDX content, ready to render, will be passed as a `children` prop to the page component. ```js:title=src/pages/blog/{mdx.frontmatter__slug}.js import * as React from 'react' import { graphql } from 'gatsby' import Layout from '../../components/layout' + import Seo from '../../components/seo' const BlogPost = ({ data, children }) => { // highlight-line return ( @@ -430,6 +437,9 @@ The JSON object in the Query Variables section should look something like the on } ` + // highlight-next-line + export const Head = ({ data }) => + export default BlogPost ``` @@ -451,6 +461,7 @@ The last step of Part 6 is to clean up your Blog page. Instead of rendering an e import * as React from 'react' import { Link, graphql } from 'gatsby' // highlight-line import Layout from '../../components/layout' + import Seo from '../../components/seo' const BlogPage = ({ data }) => { return ( @@ -488,6 +499,8 @@ The last step of Part 6 is to clean up your Blog page. Instead of rendering an e } ` + export const Head = () => + export default BlogPage ``` diff --git a/docs/docs/tutorial/part-7/index.mdx b/docs/docs/tutorial/part-7/index.mdx index 7318b68f03297..aa9ae6fe98782 100644 --- a/docs/docs/tutorial/part-7/index.mdx +++ b/docs/docs/tutorial/part-7/index.mdx @@ -370,6 +370,8 @@ Once you have your GraphQL query set up, you can add it to your blog post page t ` // highlight-end + export const Head = ({ data }) => + export default BlogPost ``` @@ -380,6 +382,7 @@ import * as React from 'react' import { graphql } from 'gatsby' import { GatsbyImage, getImage } from 'gatsby-plugin-image' // highlight-line import Layout from '../../components/layout' +import Seo from '../../components/seo' // ... ``` @@ -477,6 +480,8 @@ export const query = graphql` ... ` +export const Head = ({ data }) => + export default BlogPost ``` diff --git a/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/facebook.png b/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/facebook.png deleted file mode 100644 index 15c66c0b7fdf8..0000000000000 Binary files a/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/facebook.png and /dev/null differ diff --git a/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/google.png b/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/google.png deleted file mode 100644 index 1c609bbaa8b0c..0000000000000 Binary files a/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/google.png and /dev/null differ diff --git a/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/seo.jpg b/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/seo.jpg deleted file mode 100644 index a3ff4da9dc508..0000000000000 Binary files a/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/seo.jpg and /dev/null differ diff --git a/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/slack.png b/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/slack.png deleted file mode 100644 index 5577010df2e36..0000000000000 Binary files a/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/slack.png and /dev/null differ diff --git a/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/twitter.png b/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/twitter.png deleted file mode 100644 index 49a7a645c954a..0000000000000 Binary files a/docs/tutorial/seo-and-social-sharing-cards-tutorial/images/twitter.png and /dev/null differ diff --git a/docs/tutorial/seo-and-social-sharing-cards-tutorial/index.md b/docs/tutorial/seo-and-social-sharing-cards-tutorial/index.md deleted file mode 100644 index 2e424cb37e09d..0000000000000 --- a/docs/tutorial/seo-and-social-sharing-cards-tutorial/index.md +++ /dev/null @@ -1,638 +0,0 @@ ---- -title: Search Engine Optimization (SEO) and Social Sharing Cards with Gatsby ---- - -Perhaps you've been approached by an SEO _expert_ who can maximize your revenue and page views by following these **Three Simple Tricks**! Relatively few people make the concerted effort to implement SEO in their web app. This tutorial will share some of the ins and outs of SEO and how you can implement common SEO patterns in your Gatsby web app, today. By the end of this post you'll know how to do the following: - -- Implement SEO patterns with [react-helmet][react-helmet] -- Create an optimized social sharing card for Twitter, Facebook, and Slack -- Tweak the SEO component exposed in the default gatsby starter ([`gatsby-starter-default`][gatsby-starter-default]) - -## Implementation - -A core ingredient for SEO is a meaningful `title` tag. Make sure to include related keywords without falling into buzzword bingo. Some crawlers also respect `meta` tags, while Google seems to ignore these tags for ranking and indexing at all. - -You probably have seen something like the following: - -```html -My Wonderful App - - -``` - -The _bare minimum_ requirement is to include a `title` tag for basic SEO. However, the following describes a powerful combo of content rendered at _build time_ powered by Gatsby and GraphQL. - -## Gatsby + GraphQL - -GraphQL is a crucial feature enabled via Gatsby (note: you don't [_have_ to use GraphQL with Gatsby][unstructured-data]). Leveraging GraphQL to query your indexable content--wherever it lives (at build time!)--is one of the most powerful and flexible techniques enabled via Gatsby. The following sections are a brief look at the implementation of an extensible and flexible SEO component. - -### `StaticQuery` - -Gatsby distinguishes between page-level queries and component queries. The former can use page GraphQL queries while the latter can use [`StaticQuery`][gatsby-static-query]. A StaticQuery will be parsed, evaluated, and injected at _build time_ into the component that is requesting the data, allowing to fall back to sane defaults, while also providing an extensible, reusable component. - -### Creating the SEO component - -Using the power and flexibility of React, you can create a React component to power this functionality. - -> Note: `react-helmet` is enabled, by default, in gatsby-starter-default and gatsby-starter-blog. -> -> If you're not using those starters, [follow this guide for installation instructions][gatsby-plugin-react-helmet] - -```jsx:title=src/components/seo.js -import React from "react" -// highlight-start -import { Helmet } from "react-helmet" -import { useStaticQuery, graphql } from "gatsby" -// highlight-end - -function SEO({ description }) { - const { site } = useStaticQuery( - graphql` - query { - # highlight-start - site { - siteMetadata { - title - description - author - keywords - siteUrl - } - } - # highlight-end - } - ` - ) - - const metaDescription = description || site.siteMetadata.description - - return null -} - -export default SEO -``` - -This component doesn't _do_ anything yet, but it's the foundation for a useful, extensible component. It leverages the `useStaticQuery` functionality enabled via Gatsby to query siteMetadata (e.g. details in `gatsby-config.js`) with description and keywords. At this point, the `SEO` component returns `null` to render nothing. Next, you will _actually_ render something and build out the prototype for this SEO component. - -```jsx:title=src/components/seo.js -import React from "react" -import { Helmet } from "react-helmet" -import { useStaticQuery, graphql } from "gatsby" - -function SEO({ description, lang, meta }) { - const { site } = useStaticQuery( - graphql` - query { - site { - siteMetadata { - title - description - author - keywords - siteUrl - } - } - } - ` - ) - - const metaDescription = description || site.siteMetadata.description - - return ( - - ) -} - -// highlight-start -SEO.defaultProps = { - lang: `en`, - meta: [], - description: ``, -} -// highlight-end - -export default SEO -``` - -Whew, getting closer! This will now render the `meta` `description` tag, and will do so using content injected at build-time with the `useStaticQuery` hook. Additionally, it will add the `lang="en"` attribute to the root-level `html` tag to silence that pesky Lighthouse warning 😉. - -This is still the bare bones, rudimentary approach to SEO. An additional step is to enhance this functionality and get some useful functionality for sharing a page via social networks like Facebook, Twitter, and Slack. - -### Implementing social SEO - -In addition to SEO for actual _search_ engines you also want those pretty cards that social networks like Twitter and Slack enable. Specifically, the implementation should feature: - -- Description for embedded results -- Title for embedded results -- (Optionally) display an image and a card if an image is passed in to the component - -```jsx:title=src/components/seo.js -import React from "react" -import PropTypes from "prop-types" // highlight-line -import { Helmet } from "react-helmet" -import { useStaticQuery, graphql } from "gatsby" - -// highlight-next-line -function SEO({ description, lang, meta, image: metaImage, title }) { - const { site } = useStaticQuery( - graphql` - query { - site { - siteMetadata { - title - description - author - keywords - siteUrl - } - } - } - ` - ) - - const metaDescription = description || site.siteMetadata.description - // highlight-start - const image = - metaImage && metaImage.src - ? `${site.siteMetadata.siteUrl}${metaImage.src}` - : null - // highlight-end - - return ( - - ) -} - -SEO.defaultProps = { - lang: `en`, - meta: [], - description: ``, -} - -SEO.propTypes = { - description: PropTypes.string, - lang: PropTypes.string, - meta: PropTypes.arrayOf(PropTypes.object), - title: PropTypes.string.isRequired, - // highlight-start - image: PropTypes.shape({ - src: PropTypes.string.isRequired, - height: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - }), - // highlight-end -} - -export default SEO -``` - -Woo hoo! You enabled not only SEO for search engines like Google and Bing, but you also laid the groundwork for enhanced sharing capabilities on social networks. Finally, you will learn to add support for one of the more useful functionalities for SEO: a canonical link. - -## `link rel="canonical"` - -A canonical link is a hint to a search engine that this is the _source_ for this content. It helps resolve duplicate content issues. For instance, if you have several paths to the same content, you can use a canonical link as akin to a soft redirect which will **not** harm your search ranking if implemented correctly. - -To implement this functionality, you need to do the following: - -1. Enable passing a `pathname` prop to your SEO component -2. Prefix your `pathname` prop with your `siteUrl` (from `gatsby-config.js`) - - A canonical link should be _absolute_ (e.g. `https://your-site.com/canonical-link`), so you will need to prefix with this `siteUrl` -3. Tie into the `link` prop of `react-helmet` to create a `` tag - -```jsx:title=src/components/seo.js -import React from "react" -import PropTypes from "prop-types" -import { Helmet } from "react-helmet" -import { useStaticQuery, graphql } from "gatsby" - -// highlight-next-line -function SEO({ description, lang, meta, image: metaImage, title, pathname }) { - const { site } = useStaticQuery( - graphql` - query { - site { - siteMetadata { - title - description - author - keywords - siteUrl - } - } - } - ` - ) - - const metaDescription = description || site.siteMetadata.description - const image = - metaImage && metaImage.src - ? `${site.siteMetadata.siteUrl}${metaImage.src}` - : null - // highlight-start - const canonical = pathname ? `${site.siteMetadata.siteUrl}${pathname}` : null - // highlight-end - - return ( - - ) -} - -SEO.defaultProps = { - lang: `en`, - meta: [], - description: ``, -} - -SEO.propTypes = { - description: PropTypes.string, - lang: PropTypes.string, - meta: PropTypes.arrayOf(PropTypes.object), - title: PropTypes.string.isRequired, - image: PropTypes.shape({ - src: PropTypes.string.isRequired, - height: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - }), - // highlight-next-line - pathname: PropTypes.string, -} - -export default SEO -``` - -Woo hoo! Lots to digest here, but you've enabled adding an _absolute_ canonical link by passing in a `pathname` prop and prefixing with `siteUrl`. - -To bring it all home, it's time to begin actually _using_ this extensible SEO component to show all of these moving parts coming together to deliver a great SEO experience. - -## Using the SEO component - -You created an extensible SEO component. It takes a `title` prop and then (optionally) `description`, `meta`, `image`, and `pathname` props. - -### In a page component - -```jsx:title=src/pages/index.js -import React from "react" - -import Layout from "../components/layout" -import SEO from "../components/seo" // highlight-line - -function Index() { - return ( - - {/* highlight-line */} -

lol - pretend this is meaningful content

-
- ) -} - -export default Index -``` - -### In a template - -In many cases, you want to build a Markdown powered blog (see: [this tutorial][gatsby-markdown-blog] for more info). Of course, you want some nice SEO as well as a nifty image for sharing on Twitter, Facebook, and Slack. The following steps are needed: - -- Create a Markdown post -- Add an image, and add it to the Markdown posts frontmatter -- Query this image with GraphQL - -#### Creating the post - -```shell -mkdir -p content/blog/2019-01-04-hello-world-seo -touch content/blog/2019-01-04-hello-world-seo/index.md -``` - -```markdown:title=content/blog/2019-01-04-hello-world-seo/index.md ---- -date: 2019-01-04 -featured: images/featured.jpg ---- - -Hello World! -``` - -#### Adding the image - -Let's see how an attached image will look like. For this tutorial, you can use the following image: - -![Scribble stones forming SEO](./images/seo.jpg) - -The image will need to be located at `content/blog/2019-01-04-hello-world-seo/images/featured.jpg`. - -Make sure to use appropriately sized images for social sharing. Facebook and Twitter have restrictions beyond which they will ignore your image. - -#### Querying with GraphQL - -```jsx:title=src/templates/blog-post.js -import React from "react" -import { Link, graphql } from "gatsby" - -import Bio from "../components/bio" -import Layout from "../components/layout" -import SEO from "../components/seo" // highlight-line -import { rhythm, scale } from "../utils/typography" - -class BlogPostTemplate extends React.Component { - render() { - const post = this.props.data.markdownRemark - const siteTitle = this.props.data.site.siteMetadata.title - const image = post.frontmatter.image - ? post.frontmatter.image.childImageSharp.resize - : null // highlight-line - - return ( - - {/* highlight-start */} - - {/* highlight-end */} -

{post.frontmatter.title}

-
- - ) - } -} - -export default BlogPostTemplate - -export const pageQuery = graphql` - # highlight-start - query BlogPostBySlug($slug: String!) { - site { - siteMetadata { - title - author - } - } - markdownRemark(fields: { slug: { eq: $slug } }) { - id - excerpt(pruneLength: 160) - html - frontmatter { - title - description - image: featured { - childImageSharp { - resize(width: 1200) { - src - height - width - } - } - } - } - } - } - # highlight-end -` -``` - -There are a few aspects worth nothing here: - -- You're using `pruneLength: 160` for the excerpt; this is because [SEO meta descriptions should be between 150-170 characters][seo-description-length] - -- This is a slick feature of Gatsby's GraphQL capabilities, and will truncate (e.g. with a trailing `...`) appropriately. Perfect! - -- The image query is intentionally simplified, but a good base to build upon. There are specific size and aspect ratio requirements for [both Facebook][facebook-og-image] and [Twitter][twitter-image]. - -## The Payoff - -Using the techniques outlined in this post, you've made your Gatsby application SEO-friendly as well as sharable on common social networks. Check out the following examples of a sample blog post. - -### Google - -![Indexing Results by Google](./images/google.png) - -### Facebook - -![Social Sharing Card as on Facebook](./images/facebook.png) - -### Twitter - -![Social Sharing Card as on Twitter](./images/twitter.png) - -### Slack - -![Social Sharing Card as on Slack](./images/slack.png) - -To learn more about these validations, check out how to _validate_ SEO with the following tools from [Google][google-validation], [Twitter][twitter-validation], and [Facebook][facebook-validation]. - -The SEO resources outlined in this tutorial aren't _only_ a best practice, they're also a best practice enabled, by default. Available **today** in `gatsby-starter-default`, use: - -```shell -npm gatsby new my-new-gatsby-app -``` - -and you'll have the `SEO` component available to maximize your SEO and social sharing capabilities. Check it out! - -## Further Learning - -This tutorial is merely a shallow dive into the depths of SEO. Consider it a primer for further learning and a gentle introduction to some SEO concepts with a Gatsby twist. To truly master these concepts is outside the scope of this tutorial, but it truly is fascinating stuff that can directly lead to more eyes on your content! - -### References - -- Facebook uses the [Open Graph][og] tag format -- Twitter uses `twitter:` keywords. See [Twitter Cards][twitter-cards] for more info -- Slack reads tags in the following order ([source][slack-unfurl]) - 1. oEmbed server - 2. Twitter cards tags / Facebook Open Graph tags - 3. HTML meta tags -- Both [Google][google-json-ld] and [Apple][apple-json-ld] offer support for JSON-LD, which is _not covered_ in this guide - - If you'd like to learn more, check out [this excellent guide](https://nystudio107.com/blog/json-ld-structured-data-and-erotica) for more info on JSON-LD -- Check out the [`gatsby-seo-example`][gatsby-seo-example] for a ready-to-use starter for powering your Markdown-based blog. - -[gatsby-starter-default]: https://github.com/gatsbyjs/gatsby-starter-default -[gatsby-static-query]: /docs/static-query/ -[gatsby-markdown-blog]: /docs/adding-markdown-pages/ -[gatsby-plugin-react-helmet]: /packages/gatsby-plugin-react-helmet/ -[react-helmet]: https://github.com/nfl/react-helmet -[unstructured-data]: /docs/using-gatsby-without-graphql/ -[og]: https://developers.facebook.com/docs/sharing/webmasters/#markup -[twitter-cards]: https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/abouts-cards.html -[seo-description-length]: https://yoast.com/shorter-meta-descriptions/ -[facebook-og-image]: https://developers.facebook.com/docs/sharing/best-practices#images -[twitter-image]: https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary-card-with-large-image.html -[slack-unfurl]: https://medium.com/slack-developer-blog/everything-you-ever-wanted-to-know-about-unfurling-but-were-afraid-to-ask-or-how-to-make-your-e64b4bb9254 -[google-validation]: https://support.google.com/webmasters/answer/6066468?hl=en -[twitter-validation]: https://cards-dev.twitter.com/validator -[facebook-validation]: https://developers.facebook.com/tools/debug/sharing -[gatsby-seo-example]: https://github.com/DSchau/gatsby-seo-example -[google-json-ld]: https://developers.google.com/search/docs/guides/intro-structured-data -[apple-json-ld]: https://developer.apple.com/library/archive/releasenotes/General/WhatsNewIniOS/Articles/iOS10.html#//apple_ref/doc/uid/TP40017084-DontLinkElementID_2