├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── .stylelintrc ├── LICENSE ├── README.md ├── content ├── assets │ ├── gatsby-icon.png │ └── profile-pic.png └── blog │ ├── hello-world │ ├── index.md │ └── salty_egg.jpg │ ├── hi-folks │ └── index.md │ └── my-second-post │ └── index.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby ├── createCategories.js ├── createPages.js └── createPosts.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── bio.js │ ├── layout.js │ └── seo.js ├── pages │ ├── 404.js │ └── index.js ├── templates │ ├── category.js │ ├── page.js │ └── post.js └── utils │ └── typography.js ├── static ├── favicon.ico └── robots.txt └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | public 2 | static 3 | .cache 4 | content -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier"], 3 | "plugins": ["react", "jsx-a11y", "import"], 4 | "rules": { 5 | "react/prefer-stateless-function": "off", 6 | "react/prop-types": "off", 7 | "react/no-danger": "off" 8 | }, 9 | "settings": { 10 | "import/core-modules": [] 11 | }, 12 | "env": { 13 | "browser": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional eslint cache 39 | .eslintcache 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | 44 | # Output of 'npm pack' 45 | *.tgz 46 | 47 | # Yarn Integrity file 48 | .yarn-integrity 49 | 50 | 51 | # Build Files 52 | public/ 53 | .cache/ 54 | 55 | # Gatsby context 56 | .gatsby-context.js 57 | 58 | # Bundle stats 59 | bundle-stats.json 60 | 61 | .idea/* 62 | .idea 63 | .DS_Store -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "singleQuote": false, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "indentation": 4 5 | } 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Gatsbyjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Gatsby 4 | 5 |

6 | 7 | ### 👋 Looking for a way to support live previews with Gatsby + WordPress? 8 | [Check this repo out](https://github.com/justinwhall/wordpress-gatsby-preview-starter)! 9 | 10 | # Gatsby + Headless WordPress + Netlify Starter 11 | 12 | A starter skeleton that leverages the WordPress API for [Gatsby](https://github.com/gatsbyjs/gatsby/). Support for Continuous integration with Netlify. Publishing posts call the Netlify build hook. Deploy to Netlify stage or production enviroment when updating a WordPress post or page. 13 | 14 | ## Dependencies 15 | 16 | * [WP Buildhook Deploy](https://github.com/justinwhall/littlebot-netlify) installed and activated on the source WordPress installation. 17 | 18 | #### This Project was forked from the default [Gatsby Starter](https://github.com/gatsbyjs/gatsby-starter-blog) 19 | 20 | 21 | ### [Production Demo](https://gatsby-wordpress-netlify-production.netlify.com/) 22 | ### [Stage Demo](https://gatsby-wordpress-netlify-stage.netlify.com/) 23 | 24 | ## Getting Started 25 | 1. Fork Gatsby WordPress Netlify 26 | 2. Clone your forked repository 27 | 3. `npm install --global gatsby-cli` (if you don't have Gatsby CLI installed) 28 | 4. In the root of your project yarn install 29 | 5. Open your `gatsby-config.js` file and change the baseUrl to your WordPress url 30 | 6. Run `yarn develop` -- _not_ `gatsby develop` 31 | 32 | ### Netlify 33 | _Signup for a Netlify account if you don't already have one._ 34 | 35 | 1. Create a new site 36 | 2. Select "GitHub" from "Continuous Deployment" 37 | 3. Search and select your repository 38 | 4. Click "show advanced" 39 | 5. Click "new variable" 40 | 6. Add a deploy key DEPLOY_ENV with a value of lbn_published_stage 41 | 7. Click "deploy site" 42 | 8. Under Settings > Build & Deploy click "add build hook" 43 | 9. Name something that signifies environment (stage or production) 44 | 10. (Optional) Click "site options" and then "change site name". Rename to something that signifies this is the environment (stage or production). 45 | 11. (Optional) Repeat the process above a second time to create a production environment. Change the DEPLOY_ENV to lbn_published_production Optionally rename accordingly. 46 | 47 | ### Install WordPress 48 | Install WordPress on the server of your choice or use an existing site. I recommend a stripped down theme with no front end like this. For example, this site uses [http://gatsbynetliflydemo.justinwhall.com/wp-json/](http://gatsbynetliflydemo.justinwhall.com/wp-json/) for its data source. which is no more than a stripped down _s theme. 49 | 50 | ### Install WP Buildhook Deploy plugin (Optional. Can be used without this if you don't care about building on publish.) 51 | 52 | 1. Download or clone the [WP Buildhook Deploy plugin](https://github.com/justinwhall/littlebot-netlify) and install on your source WordPress site. 53 | 2. Find your build hooks on Netlify **Settings > Build & Deploy** 54 | 3. Add build hooks to your WordPress install under **WP Admin > Settings > WP BuildHook Deploy**  55 | 56 | ### Publish! 57 | 58 | Support for Gutenberg out of the box. If you are using the classic editor, the default "Publish" metabox has been replaced with: 59 | 60 | publish 61 | 62 | If you update or publish a post with an environment checked, your post will be published to that environment. Likewise, if you update/publish with an environment unchecked, A post will be removed from that environment. For example, if you uncheck both environments and update, the post will be removed from both. If you publish/update with both environments checked, the post will be published to both. 63 | 64 | ### Example: 65 | 66 | Using this starter requires configuring the gatsby-config.js file. You really only need to change BaseUrl, and hostingWPCOM if you're using WP.com rather than WP.org 67 | 68 | ```javascript 69 | { 70 | resolve: 'gatsby-source-wordpress', 71 | options: { 72 | // The base url to your WP site. 73 | baseUrl: 'YOUR_WORDPRESS_URL', 74 | // WP.com sites set to true, WP.org set to false 75 | hostingWPCOM: false, 76 | // The protocol. This can be http or https. 77 | protocol: 'http', 78 | // Use 'Advanced Custom Fields' Wordpress plugin 79 | useACF: true, 80 | auth: {}, 81 | // Set to true to debug endpoints on 'gatsby build' 82 | verboseOutput: false 83 | } 84 | }, 85 | ``` 86 | 87 | * Update GraphQL queries to match your WordPress Content. This is the query currently on the index page. You either need to add ACF's in your WordPress to match the query (in this case Project and Date), or you need to remove those aspects of the query. The featured_media isn't a problem -- it'll work even if you have posts without featured images. 88 | 89 | ```javascript 90 | allWordpressPost { 91 | edges { 92 | node { 93 | featured_media { 94 | source_url 95 | } 96 | author { 97 | name 98 | avatar_urls { 99 | wordpress_24 100 | wordpress_48 101 | wordpress_96 102 | } 103 | } 104 | date 105 | slug 106 | title 107 | modified 108 | excerpt 109 | id 110 | acf { 111 | project 112 | date 113 | } 114 | categories { 115 | name 116 | } 117 | tags { 118 | name 119 | } 120 | content 121 | } 122 | } 123 | } 124 | ``` 125 | 126 | * Finally, you'll probably want to update the SiteConfig to match your info, because right now it has mine. 🤠 127 | 128 | -------------------------------------------------------------------------------- /content/assets/gatsby-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinwhall/gatsby-wordpress-netlify-starter/4f02159aec32f0fab54069765f60d0154d7713a6/content/assets/gatsby-icon.png -------------------------------------------------------------------------------- /content/assets/profile-pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinwhall/gatsby-wordpress-netlify-starter/4f02159aec32f0fab54069765f60d0154d7713a6/content/assets/profile-pic.png -------------------------------------------------------------------------------- /content/blog/hello-world/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello World 3 | date: "2015-05-01T22:12:03.284Z" 4 | --- 5 | 6 | This is my first post on my new fake blog! How exciting! 7 | 8 | I'm sure I'll write a lot more interesting things in the future. 9 | 10 | Oh, and here's a great quote from this Wikipedia on 11 | [salted duck eggs](http://en.wikipedia.org/wiki/Salted_duck_egg). 12 | 13 | > A salted duck egg is a Chinese preserved food product made by soaking duck 14 | > eggs in brine, or packing each egg in damp, salted charcoal. In Asian 15 | > supermarkets, these eggs are sometimes sold covered in a thick layer of salted 16 | > charcoal paste. The eggs may also be sold with the salted paste removed, 17 | > wrapped in plastic, and vacuum packed. From the salt curing process, the 18 | > salted duck eggs have a briny aroma, a gelatin-like egg white and a 19 | > firm-textured, round yolk that is bright orange-red in color. 20 | 21 | ![Chinese Salty Egg](./salty_egg.jpg) 22 | -------------------------------------------------------------------------------- /content/blog/hello-world/salty_egg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinwhall/gatsby-wordpress-netlify-starter/4f02159aec32f0fab54069765f60d0154d7713a6/content/blog/hello-world/salty_egg.jpg -------------------------------------------------------------------------------- /content/blog/hi-folks/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: New Beginnings 3 | date: "2015-05-28T22:40:32.169Z" 4 | description: This is a custom description for SEO and Open Graph purposes, rather than the default generated excerpt. Simply add a description field to the frontmatter. 5 | --- 6 | 7 | Far far away, behind the word mountains, far from the countries Vokalia and 8 | Consonantia, there live the blind texts. Separated they live in Bookmarksgrove 9 | right at the coast of the Semantics, a large language ocean. A small river named 10 | Duden flows by their place and supplies it with the necessary regelialia. 11 | 12 | ## On deer horse aboard tritely yikes and much 13 | 14 | The Big Oxmox advised her not to do so, because there were thousands of bad 15 | Commas, wild Question Marks and devious Semikoli, but the Little Blind Text 16 | didn’t listen. She packed her seven versalia, put her initial into the belt and 17 | made herself on the way. 18 | 19 | - This however showed weasel 20 | - Well uncritical so misled 21 | - this is very interesting 22 | - Goodness much until that fluid owl 23 | 24 | When she reached the first hills of the **Italic Mountains**, she had a last 25 | view back on the skyline of her hometown _Bookmarksgrove_, the headline of 26 | [Alphabet Village](http://google.com) and the subline of her own road, the Line 27 | Lane. Pityful a rethoric question ran over her cheek, then she continued her 28 | way. On her way she met a copy. 29 | 30 | ### Overlaid the jeepers uselessly much excluding 31 | 32 | But nothing the copy said could convince her and so it didn’t take long until a 33 | few insidious Copy Writers ambushed her, made her drunk with 34 | [Longe and Parole](http://google.com) and dragged her into their agency, where 35 | they abused her for their projects again and again. And if she hasn’t been 36 | rewritten, then they are still using her. 37 | 38 | > Far far away, behind the word mountains, far from the countries Vokalia and 39 | > Consonantia, there live the blind texts. Separated they live in Bookmarksgrove 40 | > right at the coast of the Semantics, a large language ocean. 41 | 42 | It is a paradisematic country, in which roasted parts of sentences fly into your 43 | mouth. Even the all-powerful Pointing has no control about the blind texts it is 44 | an almost unorthographic life One day however a small line of blind text by the 45 | name of Lorem Ipsum decided to leave for the far World of Grammar. 46 | 47 | ### According a funnily until pre-set or arrogant well cheerful 48 | 49 | The Big Oxmox advised her not to do so, because there were thousands of bad 50 | Commas, wild Question Marks and devious Semikoli, but the Little Blind Text 51 | didn’t listen. She packed her seven versalia, put her initial into the belt and 52 | made herself on the way. 53 | 54 | 1. So baboon this 55 | 2. Mounted militant weasel gregariously admonishingly straightly hey 56 | 3. Dear foresaw hungry and much some overhung 57 | 4. Rash opossum less because less some amid besides yikes jeepers frenetic 58 | impassive fruitlessly shut 59 | 60 | When she reached the first hills of the Italic Mountains, she had a last view 61 | back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet 62 | Village and the subline of her own road, the Line Lane. Pityful a rethoric 63 | question ran over her cheek, then she continued her way. On her way she met a 64 | copy. 65 | 66 | > The copy warned the Little Blind Text, that where it came from it would have 67 | > been rewritten a thousand times and everything that was left from its origin 68 | > would be the word "and" and the Little Blind Text should turn around and 69 | > return to its own, safe country. 70 | 71 | But nothing the copy said could convince her and so it didn’t take long until a 72 | few insidious Copy Writers ambushed her, made her drunk with Longe and Parole 73 | and dragged her into their agency, where they abused her for their projects 74 | again and again. And if she hasn’t been rewritten, then they are still using 75 | her. Far far away, behind the word mountains, far from the countries Vokalia and 76 | Consonantia, there live the blind texts. 77 | 78 | #### Silent delightfully including because before one up barring chameleon 79 | 80 | Separated they live in Bookmarksgrove right at the coast of the Semantics, a 81 | large language ocean. A small river named Duden flows by their place and 82 | supplies it with the necessary regelialia. It is a paradisematic country, in 83 | which roasted parts of sentences fly into your mouth. 84 | 85 | Even the all-powerful Pointing has no control about the blind texts it is an 86 | almost unorthographic life One day however a small line of blind text by the 87 | name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox 88 | advised her not to do so, because there were thousands of bad Commas, wild 89 | Question Marks and devious Semikoli, but the Little Blind Text didn’t listen. 90 | 91 | ##### Wherever far wow thus a squirrel raccoon jeez jaguar this from along 92 | 93 | She packed her seven versalia, put her initial into the belt and made herself on 94 | the way. When she reached the first hills of the Italic Mountains, she had a 95 | last view back on the skyline of her hometown Bookmarksgrove, the headline of 96 | Alphabet Village and the subline of her own road, the Line Lane. Pityful a 97 | rethoric question ran over her cheek, then she continued her way. On her way she 98 | met a copy. 99 | 100 | ###### Slapped cozy a that lightheartedly and far 101 | 102 | The copy warned the Little Blind Text, that where it came from it would have 103 | been rewritten a thousand times and everything that was left from its origin 104 | would be the word "and" and the Little Blind Text should turn around and return 105 | to its own, safe country. But nothing the copy said could convince her and so it 106 | didn’t take long until a few insidious Copy Writers ambushed her, made her drunk 107 | with Longe and Parole and dragged her into their agency, where they abused her 108 | for their projects again and again. 109 | -------------------------------------------------------------------------------- /content/blog/my-second-post/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My Second Post! 3 | date: "2015-05-06T23:46:37.121Z" 4 | --- 5 | 6 | Wow! I love blogging so much already. 7 | 8 | Did you know that "despite its name, salted duck eggs can also be made from 9 | chicken eggs, though the taste and texture will be somewhat different, and the 10 | egg yolk will be less rich."? 11 | ([Wikipedia Link](http://en.wikipedia.org/wiki/Salted_duck_egg)) 12 | 13 | Yeah, I didn't either. 14 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | // custom typefaces 2 | import "typeface-montserrat" 3 | import "typeface-merriweather" 4 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: `WordPress Gatsby Starter`, 4 | author: `Justin W Hall`, 5 | description: `A Gatsby WordPress Starter with special love for Netlify`, 6 | siteUrl: `https://gatsby-wordpress-netlify-production.netlify.com`, 7 | social: { 8 | twitter: `justinwhall`, 9 | }, 10 | postPrefix : '/blog', 11 | pagePrefix: '', 12 | }, 13 | plugins: [ 14 | { 15 | resolve: `gatsby-source-filesystem`, 16 | options: { 17 | path: `${__dirname}/content/blog`, 18 | name: `blog`, 19 | }, 20 | }, 21 | { 22 | resolve: `gatsby-source-filesystem`, 23 | options: { 24 | path: `${__dirname}/content/assets`, 25 | name: `assets`, 26 | }, 27 | }, 28 | { 29 | resolve: 'gatsby-source-wordpress', 30 | options: { 31 | // The base url to your WP site. 32 | baseUrl: 'gatsbynetliflydemo.justinwhall.com', 33 | // baseUrl: 'data.justinwhall.com', 34 | // baseUrl: 'wpgatsby.wtf', 35 | // WP.com sites set to true, WP.org set to false 36 | hostingWPCOM: false, 37 | // The protocol. This can be http or https. 38 | protocol: 'https', 39 | // Use 'Advanced Custom Fields' Wordpress plugin 40 | useACF: false, 41 | auth: {}, 42 | // Set to true to debug endpoints on 'gatsby build' 43 | verboseOutput: true, 44 | excludedRoutes: [ 45 | "/*/*/comments", 46 | "/yoast/**", 47 | "/oembed/*" 48 | ], 49 | normalizer: function({ entities }) { 50 | return entities 51 | }, 52 | } 53 | }, 54 | `gatsby-transformer-sharp`, 55 | `gatsby-plugin-sharp`, 56 | { 57 | resolve: `gatsby-plugin-google-analytics`, 58 | options: { 59 | //trackingId: `ADD YOUR TRACKING ID HERE`, 60 | }, 61 | }, 62 | `gatsby-plugin-offline`, 63 | `gatsby-plugin-react-helmet`, 64 | { 65 | resolve: `gatsby-plugin-typography`, 66 | options: { 67 | pathToConfigModule: `src/utils/typography`, 68 | }, 69 | }, 70 | ], 71 | } 72 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const DEPLOY_ENV = process.env.DEPLOY_ENV || 'lbn_published_production'; 2 | 3 | /** 4 | * Generate node edges 5 | * 6 | * @param {any} { node, actions, getNode } 7 | */ 8 | exports.onCreateNode = ({ node, actions }) => { 9 | const { createNodeField } = actions; 10 | 11 | /** 12 | * If these don't exist, the LBN WordPress Plugin isn't installed – so build all posts. 13 | */ 14 | if ( 15 | !Object.prototype.hasOwnProperty.call(node, 'meta') || 16 | !Object.prototype.hasOwnProperty.call(node.meta, 'lbn_published_production') 17 | ) { 18 | createNodeField({ node, name: 'deploy', value: true }); 19 | return; 20 | } 21 | 22 | let deploy; 23 | 24 | if (node.meta[DEPLOY_ENV]) { 25 | deploy = true; 26 | } else { 27 | deploy = false; 28 | } 29 | 30 | createNodeField({ node, name: 'deploy', value: deploy }); 31 | }; 32 | 33 | 34 | const createPosts = require('./gatsby/createPosts'); 35 | const createPages = require('./gatsby/createPages'); 36 | const createCategories = require('./gatsby/createCategories'); 37 | 38 | exports.createPages = async ({ actions, graphql }) => { 39 | await createPosts({ actions, graphql }); 40 | await createPages({ actions, graphql }); 41 | await createCategories({ actions, graphql }); 42 | } -------------------------------------------------------------------------------- /gatsby/createCategories.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require(`lodash`) 3 | const path = require(`path`) 4 | 5 | /** 6 | * Create WordPress Category Pages 7 | */ 8 | module.exports = async ({ actions, graphql }) => { 9 | 10 | const { createPage } = actions 11 | const categoriesTemplate = path.resolve(`./src/templates/category.js`) 12 | 13 | return graphql( 14 | ` 15 | { 16 | allWordpressCategory { 17 | edges { 18 | node { 19 | id 20 | link 21 | path 22 | name 23 | count 24 | slug 25 | } 26 | } 27 | } 28 | } 29 | ` 30 | ).then(result => { 31 | if (result.errors) { 32 | throw result.errors 33 | } 34 | 35 | const categories = result.data.allWordpressCategory.edges; 36 | categories.forEach(cat => { 37 | createPage({ 38 | path: `/category/${cat.node.slug}/`, 39 | component: categoriesTemplate, 40 | context: { 41 | slug: cat.node.slug, 42 | name: cat.node.name, 43 | } 44 | }) 45 | }) 46 | 47 | // ==== END POSTS ==== 48 | return null 49 | }) 50 | } -------------------------------------------------------------------------------- /gatsby/createPages.js: -------------------------------------------------------------------------------- 1 | const path = require(`path`); 2 | 3 | /** 4 | * Create WordPress Posts 5 | */ 6 | module.exports = async ({ actions, graphql }) => { 7 | 8 | const { createPage } = actions; 9 | const postTemplate = path.resolve(`./src/templates/page.js`); 10 | 11 | return graphql( 12 | ` 13 | { 14 | site { 15 | siteMetadata { 16 | pagePrefix 17 | } 18 | } 19 | allWordpressPage { 20 | edges { 21 | node { 22 | id 23 | slug 24 | fields { 25 | deploy 26 | } 27 | } 28 | } 29 | } 30 | } 31 | ` 32 | ).then(result => { 33 | if (result.errors) { 34 | throw result.errors 35 | } 36 | 37 | const { pagePrefix } = result.data.site.siteMetadata; 38 | const { edges } = result.data.allWordpressPage; 39 | 40 | edges.forEach( edge => { 41 | if (edge.node.fields.deploy) { 42 | createPage({ 43 | path: `${pagePrefix}/${edge.node.slug}`, 44 | component: postTemplate, 45 | context: { 46 | id: edge.node.id, 47 | } 48 | }) 49 | } 50 | }) 51 | // ==== END POSTS ==== 52 | return null; 53 | }) 54 | } -------------------------------------------------------------------------------- /gatsby/createPosts.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require(`path`); 3 | 4 | /** 5 | * Create WordPress Posts 6 | */ 7 | module.exports = async ({ actions, graphql }) => { 8 | 9 | const { createPage } = actions; 10 | const postTemplate = path.resolve(`./src/templates/post.js`); 11 | 12 | return graphql( 13 | ` 14 | { 15 | site { 16 | siteMetadata { 17 | postPrefix 18 | } 19 | } 20 | allWordpressPost { 21 | edges { 22 | node { 23 | id 24 | slug 25 | modified 26 | categories { 27 | name 28 | } 29 | fields { 30 | deploy 31 | } 32 | } 33 | } 34 | } 35 | } 36 | ` 37 | ).then(result => { 38 | if (result.errors) { 39 | throw result.errors 40 | } 41 | 42 | const { postPrefix } = result.data.site.siteMetadata; 43 | const { edges } = result.data.allWordpressPost; 44 | 45 | edges.forEach( edge => { 46 | if (edge.node.fields.deploy) { 47 | createPage({ 48 | path: `${postPrefix}/${edge.node.slug}`, 49 | component: postTemplate, 50 | context: { 51 | id: edge.node.id, 52 | } 53 | }) 54 | } 55 | }) 56 | // ==== END POSTS ==== 57 | return null; 58 | }) 59 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-wordpress-netlify-starter", 3 | "description": "A starter for a blog powered by Gatsby and Markdown", 4 | "version": "2.0.0", 5 | "author": "Justin W Hall", 6 | "keywords": [ 7 | "gatsby" 8 | ], 9 | "license": "MIT", 10 | "main": "n/a", 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:justinwhall/gatsby-wordpress-netlify-starter.git" 14 | }, 15 | "scripts": { 16 | "develop": "DEPLOY_ENV=lbn_published_production gatsby develop", 17 | "build": "gatsby build", 18 | "develop": "gatsby develop", 19 | "format": "prettier --write src/**/*.{js,jsx}", 20 | "start": "npm run develop", 21 | "serve": "gatsby serve" 22 | }, 23 | "dependencies": { 24 | "gatsby": "^2.8.5", 25 | "gatsby-image": "^2.1.2", 26 | "gatsby-plugin-feed": "^2.2.2", 27 | "gatsby-plugin-google-analytics": "^2.0.20", 28 | "gatsby-plugin-manifest": "^2.1.1", 29 | "gatsby-plugin-offline": "^2.1.1", 30 | "gatsby-plugin-react-helmet": "^3.0.12", 31 | "gatsby-plugin-sharp": "^2.1.3", 32 | "gatsby-plugin-typography": "^2.2.13", 33 | "gatsby-remark-copy-linked-files": "^2.0.13", 34 | "gatsby-remark-images": "^3.0.14", 35 | "gatsby-remark-prismjs": "^3.2.10", 36 | "gatsby-remark-responsive-iframe": "^2.1.1", 37 | "gatsby-remark-smartypants": "^2.0.9", 38 | "gatsby-source-filesystem": "^2.0.38", 39 | "gatsby-source-wordpress": "^3.0.64", 40 | "gatsby-transformer-remark": "^2.3.12", 41 | "gatsby-transformer-sharp": "^2.1.21", 42 | "lodash": "^4.17.11", 43 | "prismjs": "^1.16.0", 44 | "react": "^16.8.6", 45 | "react-dom": "^16.8.6", 46 | "react-helmet": "^5.2.1", 47 | "react-typography": "^0.16.19", 48 | "typeface-merriweather": "0.0.72", 49 | "typeface-montserrat": "0.0.54", 50 | "typography": "^0.16.19", 51 | "typography-theme-wordpress-2016": "^0.16.19" 52 | }, 53 | "devDependencies": { 54 | "prettier": "^1.18.0", 55 | "cli-glob": "^0.1.0", 56 | "eslint": "^3.19.0", 57 | "eslint-config-airbnb": "^15.0.2", 58 | "eslint-config-prettier": "^2.9.0", 59 | "eslint-plugin-import": "^2.8.0", 60 | "eslint-plugin-jsx-a11y": "^5.1.1", 61 | "eslint-plugin-react": "^7.5.1", 62 | "gh-pages": "^1.1.0", 63 | "remark-cli": "^4.0.0", 64 | "remark-preset-lint-recommended": "^3.0.1", 65 | "stylefmt": "^6.0.0", 66 | "stylelint": "^8.4.0", 67 | "stylelint-config-standard": "^18.0.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/bio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bio component that queries for data 3 | * with Gatsby's StaticQuery component 4 | * 5 | * See: https://www.gatsbyjs.org/docs/static-query/ 6 | */ 7 | 8 | import React from "react" 9 | import { StaticQuery, graphql } from "gatsby" 10 | import Image from "gatsby-image" 11 | 12 | import { rhythm } from "../utils/typography" 13 | 14 | function Bio() { 15 | return ( 16 | { 19 | const { author, social } = data.site.siteMetadata 20 | return ( 21 |
27 | {author} 40 |

41 | Created by {author} who lives and works in Denver, CO.
42 | {` `} 43 | 44 | You should follow him on Twitter 45 | 46 |

47 |
48 | ) 49 | }} 50 | /> 51 | ) 52 | } 53 | 54 | const bioQuery = graphql` 55 | query BioQuery { 56 | avatar: file(absolutePath: { regex: "/profile-pic.png/" }) { 57 | childImageSharp { 58 | fixed(width: 50, height: 50) { 59 | ...GatsbyImageSharpFixed 60 | } 61 | } 62 | } 63 | site { 64 | siteMetadata { 65 | author 66 | social { 67 | twitter 68 | } 69 | } 70 | } 71 | } 72 | ` 73 | 74 | export default Bio 75 | -------------------------------------------------------------------------------- /src/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link } from "gatsby" 3 | 4 | import { rhythm, scale } from "../utils/typography" 5 | 6 | const Layout = (props) => { 7 | const { location, title, children } = props 8 | const rootPath = `${__PATH_PREFIX__}/` 9 | let header 10 | 11 | if (location.pathname === rootPath) { 12 | header = ( 13 | <> 14 |

21 | 29 | {title} 30 | 31 |

32 |
40 | With ❤ for Netlify 41 |
42 | 43 | ) 44 | } else { 45 | header = ( 46 | <> 47 |

54 | 62 | {title} 63 | 64 |

65 |
73 | With ❤ for Netlify 74 |
75 | 76 | ) 77 | } 78 | return ( 79 |
87 | 100 | Get Started 108 |
{header}
109 |
{children}
110 |
111 | © {new Date().getFullYear()}, Built with 112 | {` `} 113 | Gatsby 114 | {` `} 115 | | Built by Justin W. Hall 116 |
117 |
118 | ) 119 | } 120 | 121 | export default Layout 122 | -------------------------------------------------------------------------------- /src/components/seo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SEO component that queries for data with 3 | * Gatsby's useStaticQuery React hook 4 | * 5 | * See: https://www.gatsbyjs.org/docs/use-static-query/ 6 | */ 7 | 8 | import React from "react" 9 | import PropTypes from "prop-types" 10 | import Helmet from "react-helmet" 11 | import { useStaticQuery, graphql } from "gatsby" 12 | 13 | function SEO({ description, lang, meta, title }) { 14 | const { site } = useStaticQuery( 15 | graphql` 16 | query { 17 | site { 18 | siteMetadata { 19 | title 20 | description 21 | author 22 | } 23 | } 24 | } 25 | ` 26 | ) 27 | 28 | const metaDescription = description || site.siteMetadata.description 29 | 30 | return ( 31 | 72 | ) 73 | } 74 | 75 | SEO.defaultProps = { 76 | lang: `en`, 77 | meta: [], 78 | description: ``, 79 | } 80 | 81 | SEO.propTypes = { 82 | description: PropTypes.string, 83 | lang: PropTypes.string, 84 | meta: PropTypes.arrayOf(PropTypes.object), 85 | title: PropTypes.string.isRequired, 86 | } 87 | 88 | export default SEO 89 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { graphql } from "gatsby" 3 | 4 | import Layout from "../components/layout" 5 | import SEO from "../components/seo" 6 | 7 | const NotFoundPage = (props) => { 8 | const siteTitle = props.data.site.siteMetadata.title; 9 | 10 | return ( 11 | 12 | 13 |

Not Found

14 |

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

15 |
16 | ) 17 | } 18 | 19 | export default NotFoundPage; 20 | 21 | export const pageQuery = graphql` 22 | query { 23 | site { 24 | siteMetadata { 25 | title 26 | } 27 | } 28 | } 29 | ` 30 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link, graphql } from "gatsby" 3 | 4 | import Bio from "../components/bio" 5 | import Layout from "../components/layout" 6 | import SEO from "../components/seo" 7 | import { rhythm } from "../utils/typography" 8 | 9 | const BlogIndex = (props) => { 10 | const { 11 | title, 12 | postPrefix, 13 | } = props.data.site.siteMetadata; 14 | const posts = props.data.allWordpressPost.edges; 15 | 16 | return ( 17 | 18 | 19 | 20 | {posts.map(({ node }) => { 21 | return ( 22 |
23 |

28 | 29 | {node.title} 30 | 31 |

32 | {node.date} 33 |

38 |

39 | ) 40 | })} 41 |
42 | ) 43 | } 44 | 45 | export default BlogIndex 46 | 47 | export const pageQuery = graphql` 48 | query { 49 | site { 50 | siteMetadata { 51 | title 52 | postPrefix 53 | } 54 | } 55 | allWordpressPost( 56 | filter: { 57 | fields: { 58 | deploy: {eq: true} 59 | } 60 | } 61 | limit: 100 62 | ) { 63 | edges { 64 | node { 65 | date(formatString: "MMMM DD, YYYY") 66 | slug 67 | title 68 | excerpt 69 | id 70 | featured_media { 71 | source_url 72 | } 73 | categories { 74 | name 75 | } 76 | } 77 | } 78 | } 79 | } 80 | ` 81 | -------------------------------------------------------------------------------- /src/templates/category.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link, graphql } from "gatsby" 3 | 4 | import Bio from "../components/bio" 5 | import Layout from "../components/layout" 6 | import SEO from "../components/seo" 7 | import { rhythm } from "../utils/typography" 8 | 9 | const CategoryTemplate = (props) => { 10 | const { 11 | title, 12 | postPrefix, 13 | } = props.data.site.siteMetadata; 14 | const posts = props.data.allWordpressPost.edges; 15 | 16 | return ( 17 | 18 | 22 |

Archive | {props.pageContext.name}

23 | {posts.map(({ node }) => { 24 | return ( 25 |
26 |

31 | 32 | {node.title} 33 | 34 |

35 | {node.date} 36 |

41 |

42 | ) 43 | })} 44 |
49 | 50 |
51 | ) 52 | } 53 | 54 | export default CategoryTemplate; 55 | 56 | export const pageQuery = graphql` 57 | query category($slug: String) { 58 | site { 59 | siteMetadata { 60 | title 61 | author 62 | postPrefix 63 | } 64 | } 65 | allWordpressPost(filter: {categories: {elemMatch: {slug: {eq: $slug}}}}) { 66 | edges { 67 | node { 68 | date(formatString: "MMMM DD, YYYY") 69 | slug 70 | title 71 | modified 72 | excerpt 73 | id 74 | categories { 75 | name 76 | id 77 | slug 78 | } 79 | content 80 | } 81 | } 82 | } 83 | } 84 | ` 85 | -------------------------------------------------------------------------------- /src/templates/page.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { graphql } from "gatsby" 3 | 4 | import Bio from "../components/bio" 5 | import Layout from "../components/layout" 6 | import SEO from "../components/seo" 7 | import { rhythm } from "../utils/typography" 8 | 9 | const PageTemplate = (props) => { 10 | 11 | const post = props.data.wordpressPage; 12 | const siteTitle = props.data.site.siteMetadata.title; 13 | 14 | return ( 15 | 16 | 20 |

{post.title}

21 |
22 |
27 | 28 | 29 | ) 30 | } 31 | 32 | export default PageTemplate 33 | 34 | export const pageQuery = graphql` 35 | query PageByID($id: String!) { 36 | site { 37 | siteMetadata { 38 | title 39 | author 40 | } 41 | } 42 | wordpressPage(id: { eq: $id }) { 43 | slug 44 | title 45 | id 46 | # featured_media { 47 | # source_url 48 | # } 49 | content 50 | } 51 | } 52 | ` 53 | -------------------------------------------------------------------------------- /src/templates/post.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link, graphql } from "gatsby" 3 | 4 | import Bio from "../components/bio" 5 | import Layout from "../components/layout" 6 | import SEO from "../components/seo" 7 | import { rhythm } from "../utils/typography" 8 | 9 | const PostTemplate = (props) => { 10 | 11 | const post = props.data.wordpressPost; 12 | const siteTitle = props.data.site.siteMetadata.title; 13 | let featuredImage = false; 14 | 15 | if (post.featured_media && post.featured_media.source_url ) { 16 | featuredImage = post.featured_media.source_url; 17 | } 18 | 19 | return ( 20 | 21 | 25 |

{post.title}

26 | {featuredImage && 27 | {post.title} 28 | } 29 |
35 |
{post.date}
36 | 40 | {post.categories[0].name}{' '} 41 | 42 |
43 | 44 |
45 |
50 | 51 | 52 | ) 53 | 54 | } 55 | 56 | export default PostTemplate 57 | 58 | export const pageQuery = graphql` 59 | query PostById($id: String!) { 60 | site { 61 | siteMetadata { 62 | title 63 | author 64 | } 65 | } 66 | wordpressPost(id: { eq: $id }) { 67 | date(formatString: "MMMM DD, YYYY") 68 | slug 69 | title 70 | modified 71 | excerpt 72 | id 73 | featured_media { 74 | source_url 75 | } 76 | categories { 77 | name 78 | slug 79 | } 80 | content 81 | } 82 | } 83 | ` 84 | -------------------------------------------------------------------------------- /src/utils/typography.js: -------------------------------------------------------------------------------- 1 | import Typography from "typography" 2 | import Wordpress2016 from "typography-theme-wordpress-2016" 3 | 4 | Wordpress2016.overrideThemeStyles = () => { 5 | return { 6 | "a.gatsby-resp-image-link": { 7 | boxShadow: `none`, 8 | }, 9 | } 10 | } 11 | 12 | delete Wordpress2016.googleFonts 13 | 14 | const typography = new Typography(Wordpress2016) 15 | 16 | // Hot reload typography in development. 17 | if (process.env.NODE_ENV !== `production`) { 18 | typography.injectStyles() 19 | } 20 | 21 | export default typography 22 | export const rhythm = typography.rhythm 23 | export const scale = typography.scale 24 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinwhall/gatsby-wordpress-netlify-starter/4f02159aec32f0fab54069765f60d0154d7713a6/static/favicon.ico -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | --------------------------------------------------------------------------------