diff --git a/docs/docs/images/recipe-sourcing-contentful-graphql.png b/docs/docs/images/recipe-sourcing-contentful-graphql.png new file mode 100644 index 0000000000000..d1d6a7c2da596 Binary files /dev/null and b/docs/docs/images/recipe-sourcing-contentful-graphql.png differ diff --git a/docs/docs/recipes.md b/docs/docs/recipes.md index 8d5e122cb7194..d6ee7237d2ce7 100644 --- a/docs/docs/recipes.md +++ b/docs/docs/recipes.md @@ -985,6 +985,136 @@ export const pageQuery = graphql` - [Guide to creating pages from data programmatically](/docs/programmatically-create-pages-from-data/) - [Example repo](https://github.com/gatsbyjs/gatsby/tree/master/examples/recipe-sourcing-markdown) for this recipe +### Sourcing data from Contentful + +#### Prerequisites + +- A [Gatsby site](/docs/quick-start/) +- A [Contentful account](https://www.contentful.com/) +- The [Contentful CLI](https://www.npmjs.com/package/contentful-cli) installed + +#### Directions + +1. Log in to Contentful with the CLI and follow the steps. It will help you create an account if you don't have one. + +```shell +contentful login +``` + +2. Create a new space if you don't already have one. Make sure to save the space ID given to you at the end of the command. If you already have a Contentful space and space ID, you can skip steps 2 and 3. + +Note: for new accounts, you can overwrite the default onboarding space. Check to see the [spaces included with your account](https://app.contentful.com/account/profile/space_memberships). + +```shell +contentful space create --name 'Gatsby example' +``` + +3. Seed the new space with example blog content using the new space ID returned from the previous command, in place of ``. + +```shell +contentful space seed -s '' -t blog +``` + +For example, with a space ID in place: `contentful space seed -s '22fzx88spbp7' -t blog` + +4. Create a new access token for your space. Remember this token, as you will need it in step 6. + +```shell +contentful space accesstoken create -s '' --name 'Example token' +``` + +5. Install the `gatsby-source-contentful` plugin in your Gatsby site: + +```shell +npm install --save gatsby-source-contentful +``` + +6. Edit the file `gatsby-config.js` and add the `gatsby-source-contentful` to the `plugins` array to enable the plugin. You should strongly consider using [environment variables](/docs/environment-variables/) to store your space ID and token for security purposes. + +```javascript:title=gatsby-config.js +plugins: [ + // add to array along with any other installed plugins +  // highlight-start +  { + + + resolve: `gatsby-source-contentful`, + options: { + spaceId: ``, // or process.env.CONTENTFUL_SPACE_ID + accessToken: ``, // or process.env.CONTENTFUL_TOKEN + }, + }, + // highlight-end +], +``` + +7. Run `gatsby develop` and make sure the site compiled successfully. + +8. Query data with the [GraphiQL editor](/docs/introducing-graphiql/) at `https://localhost:8000/___graphql`. The Contentful plugin adds several new node types to your site, including every content type in your Contentful website. Your example space with a "Blog Post" content type produces a `allContentfulBlogPost` node type in GraphQL. + +![the graphql interface, with a sample query outlined below](./images/recipe-sourcing-contentful-graphql.png) + +To query for Blog Post titles from Contentful, use the following GraphQL query: + +```graphql +{ + allContentfulBlogPost { + edges { + node { + title + } + } + } +} +``` + +Contentful nodes also include several metadata fields like `createdAt` or `node_locale`. + +9. To show a list of links to the blog posts, create a new file in `/src/pages/blog.js`. This page will display all posts, sorted by updated date. + +```jsx:title=src/pages/blog.js +import React from "react" +import { graphql, Link } from "gatsby" + +const BlogPage = ({ data }) => ( +
+

Blog

+
    + {data.allContentfulBlogPost.edges.map(({ node, index }) => ( +
  • + {node.title} +
  • + ))} +
+
+) + +export default BlogPage + +export const query = graphql` + { + allContentfulBlogPost(sort: { fields: [updatedAt] }) { + edges { + node { + title + slug + } + } + } + } +` +``` + +To continue building out your Contentful site including post detail pages, check out the rest of the [Gatsby docs](/docs/sourcing-from-contentful/) and additional resources below. + +#### Additional resources + +- [Building a Site with React and Contentful](/blog/2018-1-25-building-a-site-with-react-and-contentful/) +- [More on Sourcing from Contentful](/docs/sourcing-from-contentful/) +- [Contentful source plugin](/packages/gatsby-source-contentful/) +- [Long-text field types returned as objects](/packages/gatsby-source-contentful/#a-note-about-longtext-fields) +- [Example repository for this recipe](https://github.com/gatsbyjs/gatsby/tree/master/examples/recipe-sourcing-contentful) + ### Pulling data from an external source and creating pages without GraphQL You don't have to use the GraphQL data layer to include data in pages, [though there are reasons why you should consider GraphQL](/docs/why-gatsby-uses-graphql/). You can use the node `createPages` API to pull unstructured data directly into Gatsby sites rather than through GraphQL and source plugins. diff --git a/examples/recipe-sourcing-contentful/README.md b/examples/recipe-sourcing-contentful/README.md new file mode 100644 index 0000000000000..ee4c1d6b7377b --- /dev/null +++ b/examples/recipe-sourcing-contentful/README.md @@ -0,0 +1,3 @@ +# Sourcing data from Contentful recipe example + +This repo is an example Gatsby site that shows how to source data from Contentful. To get started, [follow the Gatsby recipe to create a sample Contentful space](https://www.gatsbyjs.org/docs/recipes/#sourcing-data-from-contentful). diff --git a/examples/recipe-sourcing-contentful/gatsby-config.js b/examples/recipe-sourcing-contentful/gatsby-config.js new file mode 100644 index 0000000000000..0ffef492c8cf8 --- /dev/null +++ b/examples/recipe-sourcing-contentful/gatsby-config.js @@ -0,0 +1,17 @@ +module.exports = { + siteMetadata: { + title: `Gatsby Contentful Recipe`, + description: `Example Gatsby site sourcing data from Contentful.`, + author: `@gatsbyjs`, + }, + plugins: [ + { + resolve: `gatsby-source-contentful`, + options: { + spaceId: `[space ID]`, // or process.env.CONTENTFUL_SPACE_ID + accessToken: `[access token]`, // or process.env.CONTENTFUL_TOKEN + }, + }, + `gatsby-transformer-remark`, + ], +} diff --git a/examples/recipe-sourcing-contentful/gatsby-node.js b/examples/recipe-sourcing-contentful/gatsby-node.js new file mode 100644 index 0000000000000..7d11e1319e3bb --- /dev/null +++ b/examples/recipe-sourcing-contentful/gatsby-node.js @@ -0,0 +1,30 @@ +const path = require(`path`) + +exports.createPages = async ({ graphql, actions }) => { + const { createPage } = actions + const result = await graphql(` + query { + allContentfulBlogPost { + edges { + node { + slug + title + childContentfulBlogPostBodyTextNode { + childMarkdownRemark { + html + } + } + } + } + } + } + `) + + result.data.allContentfulBlogPost.edges.forEach(({ node }) => { + createPage({ + path: `blog/${node.slug}`, + component: path.resolve(`./src/templates/blog-post.js`), + context: node, + }) + }) +} diff --git a/examples/recipe-sourcing-contentful/package.json b/examples/recipe-sourcing-contentful/package.json new file mode 100644 index 0000000000000..4cfc190506594 --- /dev/null +++ b/examples/recipe-sourcing-contentful/package.json @@ -0,0 +1,39 @@ +{ + "name": "gatsby-recipe-sourcing-contentful", + "private": true, + "description": "A simple starter to get up and developing quickly with Gatsby", + "version": "0.1.0", + "author": "@gatsbyjs", + "dependencies": { + "gatsby": "^2.15.14", + "gatsby-image": "^2.2.18", + "gatsby-plugin-offline": "^2.2.10", + "gatsby-source-contentful": "^2.1.35", + "gatsby-transformer-remark": "^2.6.22", + "prop-types": "^15.7.2", + "react": "^16.9.0", + "react-dom": "^16.9.0" + }, + "devDependencies": { + "prettier": "^1.18.2" + }, + "keywords": [ + "gatsby" + ], + "license": "MIT", + "scripts": { + "build": "gatsby build", + "develop": "gatsby develop", + "format": "prettier --write \"**/*.{js,jsx,json,md}\"", + "start": "npm run develop", + "serve": "gatsby serve", + "test": "echo \"Write tests! -> https://gatsby.dev/unit-testing \"" + }, + "repository": { + "type": "git", + "url": "https://github.com/gatsbyjs/gatsby-starter-default" + }, + "bugs": { + "url": "https://github.com/gatsbyjs/gatsby/issues" + } +} diff --git a/examples/recipe-sourcing-contentful/src/components/header.js b/examples/recipe-sourcing-contentful/src/components/header.js new file mode 100644 index 0000000000000..8990b7e31bb7a --- /dev/null +++ b/examples/recipe-sourcing-contentful/src/components/header.js @@ -0,0 +1,42 @@ +import { Link } from "gatsby" +import PropTypes from "prop-types" +import React from "react" + +const Header = ({ siteTitle }) => ( +
+
+

+ + {siteTitle} + +

+
+
+) + +Header.propTypes = { + siteTitle: PropTypes.string, +} + +Header.defaultProps = { + siteTitle: ``, +} + +export default Header diff --git a/examples/recipe-sourcing-contentful/src/components/layout.css b/examples/recipe-sourcing-contentful/src/components/layout.css new file mode 100644 index 0000000000000..b6f63320fb8ae --- /dev/null +++ b/examples/recipe-sourcing-contentful/src/components/layout.css @@ -0,0 +1,622 @@ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; +} +audio:not([controls]) { + display: none; + height: 0; +} +progress { + vertical-align: baseline; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; + -webkit-text-decoration-skip: objects; +} +a:active, +a:hover { + outline-width: 0; +} +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted; +} +b, +strong { + font-weight: inherit; + font-weight: bolder; +} +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background-color: #ff0; + color: #000; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +img { + border-style: none; +} +svg:not(:root) { + overflow: hidden; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +figure { + margin: 1em 40px; +} +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} +button, +input, +optgroup, +select, +textarea { + font: inherit; + margin: 0; +} +optgroup { + font-weight: 700; +} +button, +input { + overflow: visible; +} +button, +select { + text-transform: none; +} +[type="reset"], +[type="submit"], +button, +html [type="button"] { + -webkit-appearance: button; +} +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner, +button::-moz-focus-inner { + border-style: none; + padding: 0; +} +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring, +button:-moz-focusring { + outline: 1px dotted ButtonText; +} +fieldset { + border: 1px solid silver; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal; +} +textarea { + overflow: auto; +} +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; + padding: 0; +} +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} +[type="search"] { + -webkit-appearance: textfield; + outline-offset: -2px; +} +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-input-placeholder { + color: inherit; + opacity: 0.54; +} +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; +} +html { + font: 112.5%/1.45em georgia, serif; + box-sizing: border-box; + overflow-y: scroll; +} +* { + box-sizing: inherit; +} +*:before { + box-sizing: inherit; +} +*:after { + box-sizing: inherit; +} +body { + color: hsla(0, 0%, 0%, 0.8); + font-family: georgia, serif; + font-weight: normal; + word-wrap: break-word; + font-kerning: normal; + -moz-font-feature-settings: "kern", "liga", "clig", "calt"; + -ms-font-feature-settings: "kern", "liga", "clig", "calt"; + -webkit-font-feature-settings: "kern", "liga", "clig", "calt"; + font-feature-settings: "kern", "liga", "clig", "calt"; +} +img { + max-width: 100%; + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +h1 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 2.25rem; + line-height: 1.1; +} +h2 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 1.62671rem; + line-height: 1.1; +} +h3 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 1.38316rem; + line-height: 1.1; +} +h4 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 1rem; + line-height: 1.1; +} +h5 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 0.85028rem; + line-height: 1.1; +} +h6 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 0.78405rem; + line-height: 1.1; +} +hgroup { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +ul { + margin-left: 1.45rem; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + list-style-position: outside; + list-style-image: none; +} +ol { + margin-left: 1.45rem; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + list-style-position: outside; + list-style-image: none; +} +dl { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +dd { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +p { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +figure { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +pre { + margin-left: 0; + margin-right: 0; + margin-top: 0; + margin-bottom: 1.45rem; + font-size: 0.85rem; + line-height: 1.42; + background: hsla(0, 0%, 0%, 0.04); + border-radius: 3px; + overflow: auto; + word-wrap: normal; + padding: 1.45rem; +} +table { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + font-size: 1rem; + line-height: 1.45rem; + border-collapse: collapse; + width: 100%; +} +fieldset { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +blockquote { + margin-left: 1.45rem; + margin-right: 1.45rem; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +form { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +noscript { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +iframe { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +hr { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: calc(1.45rem - 1px); + background: hsla(0, 0%, 0%, 0.2); + border: none; + height: 1px; +} +address { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +b { + font-weight: bold; +} +strong { + font-weight: bold; +} +dt { + font-weight: bold; +} +th { + font-weight: bold; +} +li { + margin-bottom: calc(1.45rem / 2); +} +ol li { + padding-left: 0; +} +ul li { + padding-left: 0; +} +li > ol { + margin-left: 1.45rem; + margin-bottom: calc(1.45rem / 2); + margin-top: calc(1.45rem / 2); +} +li > ul { + margin-left: 1.45rem; + margin-bottom: calc(1.45rem / 2); + margin-top: calc(1.45rem / 2); +} +blockquote *:last-child { + margin-bottom: 0; +} +li *:last-child { + margin-bottom: 0; +} +p *:last-child { + margin-bottom: 0; +} +li > p { + margin-bottom: calc(1.45rem / 2); +} +code { + font-size: 0.85rem; + line-height: 1.45rem; +} +kbd { + font-size: 0.85rem; + line-height: 1.45rem; +} +samp { + font-size: 0.85rem; + line-height: 1.45rem; +} +abbr { + border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); + cursor: help; +} +acronym { + border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); + cursor: help; +} +abbr[title] { + border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); + cursor: help; + text-decoration: none; +} +thead { + text-align: left; +} +td, +th { + text-align: left; + border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); + font-feature-settings: "tnum"; + -moz-font-feature-settings: "tnum"; + -ms-font-feature-settings: "tnum"; + -webkit-font-feature-settings: "tnum"; + padding-left: 0.96667rem; + padding-right: 0.96667rem; + padding-top: 0.725rem; + padding-bottom: calc(0.725rem - 1px); +} +th:first-child, +td:first-child { + padding-left: 0; +} +th:last-child, +td:last-child { + padding-right: 0; +} +tt, +code { + background-color: hsla(0, 0%, 0%, 0.04); + border-radius: 3px; + font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono", + "Liberation Mono", Menlo, Courier, monospace; + padding: 0; + padding-top: 0.2em; + padding-bottom: 0.2em; +} +pre code { + background: none; + line-height: 1.42; +} +code:before, +code:after, +tt:before, +tt:after { + letter-spacing: -0.2em; + content: " "; +} +pre code:before, +pre code:after, +pre tt:before, +pre tt:after { + content: ""; +} +@media only screen and (max-width: 480px) { + html { + font-size: 100%; + } +} diff --git a/examples/recipe-sourcing-contentful/src/components/layout.js b/examples/recipe-sourcing-contentful/src/components/layout.js new file mode 100644 index 0000000000000..8d559875eb270 --- /dev/null +++ b/examples/recipe-sourcing-contentful/src/components/layout.js @@ -0,0 +1,52 @@ +/** + * Layout component that queries for data + * with Gatsby's useStaticQuery component + * + * See: https://www.gatsbyjs.org/docs/use-static-query/ + */ + +import React from "react" +import PropTypes from "prop-types" +import { useStaticQuery, graphql } from "gatsby" + +import Header from "./header" +import "./layout.css" + +const Layout = ({ children }) => { + const data = useStaticQuery(graphql` + query SiteTitleQuery { + site { + siteMetadata { + title + } + } + } + `) + + return ( + <> +
+
+
{children}
+
+ © {new Date().getFullYear()}, Built with + {` `} + Gatsby +
+
+ + ) +} + +Layout.propTypes = { + children: PropTypes.node.isRequired, +} + +export default Layout diff --git a/examples/recipe-sourcing-contentful/src/pages/404.js b/examples/recipe-sourcing-contentful/src/pages/404.js new file mode 100644 index 0000000000000..70bc752374a94 --- /dev/null +++ b/examples/recipe-sourcing-contentful/src/pages/404.js @@ -0,0 +1,12 @@ +import React from "react" + +import Layout from "../components/layout" + +const NotFoundPage = () => ( + +

NOT FOUND

+

You just hit a route that doesn't exist... the sadness.

+
+) + +export default NotFoundPage diff --git a/examples/recipe-sourcing-contentful/src/pages/index.js b/examples/recipe-sourcing-contentful/src/pages/index.js new file mode 100644 index 0000000000000..c2c437619e8c8 --- /dev/null +++ b/examples/recipe-sourcing-contentful/src/pages/index.js @@ -0,0 +1,31 @@ +import React from "react" +import { graphql, Link } from "gatsby" +import Layout from "../components/layout" + +const IndexPage = ({ data }) => ( + +

Contentful data source example

+
    + {data.allContentfulBlogPost.edges.map(({ node }, index) => ( +
  • + {node.title} +
  • + ))} +
+
+) + +export default IndexPage + +export const query = graphql` + { + allContentfulBlogPost(sort: { fields: [updatedAt] }) { + edges { + node { + title + slug + } + } + } + } +` diff --git a/examples/recipe-sourcing-contentful/src/templates/blog-post.js b/examples/recipe-sourcing-contentful/src/templates/blog-post.js new file mode 100644 index 0000000000000..e3c4d5fb96791 --- /dev/null +++ b/examples/recipe-sourcing-contentful/src/templates/blog-post.js @@ -0,0 +1,17 @@ +import React from "react" +import Layout from "../components/layout" + +const BlogPostTemplate = ({ pageContext }) => ( + +

{pageContext.title}

+
+ +) + +export default BlogPostTemplate