├── .gitignore ├── LICENSE ├── README.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── package-lock.json ├── package.json ├── queries.md ├── src ├── components │ ├── blogSquare.css │ ├── blogSquare.js │ ├── layout.css │ ├── layout.js │ ├── nav.css │ ├── nav.js │ └── seo.js ├── pages │ ├── 2019-08-24-cute-cats │ │ └── index.md │ ├── 2019-08-26-five-tech-skills-to-master │ │ └── index.md │ ├── 2019-09-06-hello-graphql-day-bodensee │ │ └── index.md │ ├── 2019-09-13-hey-amsterdam │ │ └── index.md │ ├── 404.js │ ├── index.js │ ├── podcasting.js │ ├── speaking.js │ └── writing.js └── templates │ ├── blogPost.css │ └── blogPost.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | .cache 64 | public/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Emma Wedekind 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 | # Building A Portfolio With Gatsby 2 | A step-by-step tutorial on how to build a portfolio with Gatsby 3 | 4 | You can view the final site [here](https://building-a-portfolio-with-gatsby.netlify.com) 5 | # Step 1: Getting Started 6 | 1. Fork & clone this repository 7 | 2. Change directories `cd building-a-portfolio-with-gatsby` 8 | 3. Install dependencies with `yarn` 9 | 4. Run `git checkout step-1` 10 | 5. Start the development server `gatsby develop` 11 | 6. Your site should look like this: 12 | ![Starting UI](https://user-images.githubusercontent.com/7671983/63641527-f7c17680-c6a7-11e9-8c8c-801f280ed795.png) 13 | 14 | _If you ever need to revert to this state, you can use `git checkout step-1` to check out the tag for this state._ 15 | 16 | # Step 2: Create Navigation 17 | We want to create a navigation sidebar with four links: 18 | - Home 19 | - Writing 20 | - Speaking 21 | - Podcasting 22 | 23 | 1. Open `nav.js` from the components folder. 24 | 2. Let's add some semantic HTML for a navigation component as the return value for our component: 25 | ```jsx 26 | 35 | ``` 36 | 37 | _I won't be covering CSS in this tutorial, but all the CSS you need is located in the respective `.css` files. To use the default styling, be sure to include the class names._ 38 | 39 | 3. Now we need to turn these navigation items into links. You might notice we've already imported the Link component from `gatsby`. 40 | ``` 41 | import { Link } from "gatsby" 42 | ``` 43 | It's important to replace local links in your Gatsby project with the `` element. If we use the anchor tag, `` for internal routing, it will cause a re-render of our content. We don't want this, because we want our site to be fast! So we use ``. You can read more about that in the [Gatsby Documentation](https://www.gatsbyjs.org/docs/gatsby-link/). 44 | 45 | ```jsx 46 |
  • Home
  • 47 |
  • Writing
  • 48 |
  • Speaking
  • 49 |
  • Podcasting
  • 50 | ``` 51 | 52 | 4. Each `` element needs to know where to route to on click, and we do this with the `to` attribute. Let's add this attribute to all of our links: 53 | ```jsx 54 |
  • Home
  • 55 |
  • Writing
  • 56 |
  • Speaking
  • 57 |
  • Podcasting
  • 58 | ``` 59 | 60 | 5. If we head over to `layout.js`, we can see we have already imported the navigation component and rendered it above the main content area. Your site should look like this: 61 | ![Navigation](https://user-images.githubusercontent.com/7671983/63641714-052c3000-c6ab-11e9-93be-d74d7e690b5c.png) 62 | 63 | 6. The last thing we want to do is apply a special style to the selected navigation link. There are [two ways](https://www.gatsbyjs.org/docs/gatsby-link/#add-custom-styles-for-the-currently-active-link) we can approach this: 64 | - Use inline styling (CSS-in-JS) 65 | - Use a class name with an external stylesheet 66 | 67 | To apply an inline style when the link is active, we can use `activeStyle`. 68 | 69 | To apply an active class name when the link is active, we can use `activeClassName`. 70 | 71 | For this tutorial we'll use the second approach of applying an active class, but either approach is valid. 72 | 73 | Add the following attribute to each link: 74 | ```jsx 75 | activeClassName="nav__link--active" 76 | ``` 77 | 78 | Your `nav.js` file should now look like this: 79 | ```jsx 80 | import React from "react"; 81 | import { Link } from "gatsby"; 82 | 83 | import "./nav.css"; 84 | 85 | const Nav = () => ( 86 | 95 | ) 96 | 97 | export default Nav; 98 | ``` 99 | 100 | ![Finished nav](https://user-images.githubusercontent.com/7671983/63641772-c0ed5f80-c6ab-11e9-9595-3bef54b6c17b.png) 101 | 102 | _If you ever need to revert to this state, you can use `git checkout step-2` to check out the tag for this state._ 103 | 104 | # Step 3: Rendering Blog Posts 105 | Having a blog on your portfolio is a great way to showcase your work. So let's add a blog to our portfolio! 106 | 107 | Gatsby works with GraphQL to do all sorts of robust data fetching. We won't delve into GraphQL in this blog post, but you can check out the [documentation](https://www.gatsbyjs.org/docs/querying-with-graphql/). 108 | 109 | When you run `gatsby develop`, you might have noticed two URLs posted in the terminal: 110 | - The development server: [http://localhost:8000/](http://localhost:8000/) 111 | - The GraphiQL playground: [http://localhost:8000/___graphql](http://localhost:8000/___graphql) 112 | 113 | ![Terminal output](https://user-images.githubusercontent.com/7671983/63641825-7e785280-c6ac-11e9-96fe-06bee72eb39f.png) 114 | 115 | GraphiQL allows you to play around with GraphQL queries in your browser. So let's make some queries. 116 | 117 | 1. Open [http://localhost:8000/___graphql](http://localhost:8000/___graphql) in your browser. 118 | 119 | Here is what the GraphiQL UI looks like: 120 | ![GraphiQL](https://user-images.githubusercontent.com/7671983/63641844-cd25ec80-c6ac-11e9-9fdd-d6125dbeebef.png) 121 | 122 | I won't be delving into [GraphQL](https://graphql.org/) or [GraphiQL](https://electronjs.org/apps/graphiql) in this post. You can read the online documentation for more information. 123 | 124 | 2. We want to create two queries: 125 | - One query for retrieving a list of all blog posts 126 | - One query for retrieving details and markup for individual blog posts 127 | 128 | 3. First, we need to install a plug-in to work with our markdown files. We will use the [gatsby-transformer-remark](https://www.gatsbyjs.org/packages/gatsby-transformer-remark/) plug-in to accomplish this task. 129 | ``` 130 | yarn add gatsby-transformer-remark gatsby-source-filesystem 131 | ``` 132 | 133 | 4. Now that we've installed the plug-in as a dependency, we need to tell Gatsby to actually use it. 134 | 135 | Open `gatsby-config.js` and inside of the `plugins` array, add `gatsby-transformer-remark`. 136 | 137 | ``` 138 | plugins: [ 139 | ... 140 | `gatsby-transformer-remark`, 141 | ... 142 | ] 143 | ``` 144 | 145 | 5. We also need to configure the plug-in [gatsby-source-filesystem](https://www.gatsbyjs.org/packages/gatsby-source-filesystem/). It's already installed as a dependency, so head over to `gatsby-config.js`. 146 | 147 | Create a new object inside of the `plugins` array and add the following: 148 | ``` 149 | { 150 | resolve: 'gatsby-source-filesystem', 151 | options: { 152 | path: `${__dirname}/src/pages`, 153 | name: 'pages', 154 | }, 155 | }, 156 | ``` 157 | 158 | This plug-in will transform our markdown files into `Markdown Remark` nodes so we can query their HTML and frontmatter. 159 | 160 | 5. Stop and restart your development server. Each time we alter the `gatsby-config.js` file, we have to restart our server. 161 | 162 | Now, we're ready to play with some GraphQL queries. 163 | 164 | ## Retrieving All Blog Posts 165 | We will use the `AllMarkdownRemark` query to retrieve a list of all the blog posts. 166 | 1. Inside of GraphiQL, delete the boilerplate comments from the editor. 167 | 168 | 2. We can use the explorer on the left to select what we want to query for. 169 | 170 | Using the explorer in the left-hand sidebar, select the following: 171 | ``` 172 | allMarkdownRemark 173 | | edges 174 | > node 175 | > frontmatter 176 | - date 177 | - description 178 | - title 179 | - path 180 | - id 181 | ``` 182 | 3. Change the name of the query in the main text editor from `MyQuery` to `AllBlogPosts`. 183 | 184 | 4. Press the play button to see the query in action. 185 | 186 | You should now see data for two blog posts: 187 | 188 | ![Blog post data](https://user-images.githubusercontent.com/7671983/63642008-dc5a6980-c6af-11e9-858b-68b8816efcd6.png) 189 | 190 | 5. Head over to `writing.js` and let's use our newly created query. 191 | 192 | Right above the default export of the writing page, add the following: 193 | 194 | ```jsx 195 | export const allBlogsQuery = graphql` 196 | query AllBlogPosts { 197 | allMarkdownRemark { 198 | edges { 199 | node { 200 | frontmatter { 201 | date 202 | description 203 | title 204 | path 205 | } 206 | id 207 | } 208 | } 209 | } 210 | } 211 | ` 212 | ``` 213 | 214 | 6. You'll notice we already have a de-structured prop being passed into the writing page. This is the data that results from our GraphQL query. We can map over this data using a JSX expression. 215 | 216 | To ensure our data is comning in as expected, add the following code underneath the `

    ` tag: 217 | 218 | ```jsx 219 | {data.allMarkdownRemark.edges.map(post => ( 220 | document.write(post.node.frontmatter.title) 221 | ))} 222 | ``` 223 | 224 | 7. If we head over to the browser and go to the writing page, we should see names of our two blog posts: 225 | ![Blog data](https://user-images.githubusercontent.com/7671983/63642049-8df99a80-c6b0-11e9-9a1f-9eda58bb0047.png) 226 | 227 | 8. Now we want to actually make this data look nice. We will create a structure for each blog post with the `blogSquare.js` component. 228 | 229 | Inside `blogSquare.js`, we want to display some data about each blog post: 230 | - title 231 | - date 232 | - path 233 | -description 234 | 235 | Let's go ahead and add these as de-structured properties on the component. We'll pass them in later. 236 | 237 | ```jsx 238 | const BlogSquare = ({ title, date, path, description }) => ( 239 |
    240 | ) 241 | ``` 242 | 243 | 9. Let's add some semantic markup to display these properties nicely: 244 | 245 | ```jsx 246 |
    247 |

    {title}

    248 |

    {date}

    249 |

    {description}

    250 |

    Read more

    251 |
    252 | ``` 253 | 254 | 10. We want to have each blog square link to the full blog post. So using the `` component we introduced earlier, let's wrap the inner HTML in a `` component. 255 | 256 | We want the blog square to link to the post, so we'll use the `path` variable for the `to` attribute.: 257 | 258 | ```jsx 259 |
    260 | 261 |

    {title}

    262 |

    {date}

    263 |

    {description}

    264 |

    Read more

    265 | 266 |
    267 | ``` 268 | 269 | 11. Lastly, let's add some inline styling to make this blog square look a little nicer: 270 | 271 | ```jsx 272 |
    273 | 277 |

    {title}

    278 |

    {date}

    279 |

    {description}

    280 |

    Read more

    281 | 282 |
    283 | ``` 284 | 285 | _You can also use class names, as there is a linked stylesheet, but I wanted to demonstrate the inline styling approach._ 286 | 287 | Here is the final state of `blogSquare.js`: 288 | 289 | ```jsx 290 | import React from 'react'; 291 | import { Link } from "gatsby"; 292 | 293 | import './blogSquare.css' 294 | 295 | const BlogSquare = ({ title, date, path, description }) => ( 296 |
    297 | 301 |

    {title}

    302 |

    {date}

    303 |

    {description}

    304 |

    Read more

    305 | 306 |
    307 | ) 308 | 309 | export default BlogSquare 310 | ``` 311 | 312 | 12. Now that we have a component to render our blog data, let's use it! 313 | 314 | Inside of `writing.js`, the component is already imported, so we can just jump right in. 315 | 316 | Within our `map` function, let's render a `` component for each blog post. Remove the `document.write` we just included to test our data, and replace it with the following: 317 | 318 | ```jsx 319 | 326 | ``` 327 | 328 | Remember, we included the four properties inside of `blogSquare.js` (title, date, description, path), so they must be passed with the data for each post. 329 | 330 | Additionally, when mapping over an array of data and rendering JSX for each iteration, we must include a unique `key` property. 331 | 332 | Let's check out if our blog posts are being rendered: 333 | ![Blog posts](https://user-images.githubusercontent.com/7671983/63642113-20e70480-c6b2-11e9-84c0-cbeb88100c68.png) 334 | 335 | Great! but we get a 404 error if we click the "Read more" link. 336 | 337 | That's because we haven't created the query for our individual blog posts. 338 | 339 | 340 | ## Retrieving An Individual Blog Post 341 | 13. Back in [GraphiQL](http://localhost:8000/___graphql), let's create a new query. 342 | 343 | Using the explorer in the left-hand sidebar, select the following: 344 | ``` 345 | markdownRemark 346 | > frontmatter 347 | - date 348 | - description 349 | - path 350 | - title 351 | - html 352 | - id 353 | ``` 354 | 355 | 14. Change the name of the query to `BlogPost`. 356 | 357 | 15. We'll need to pass data for an individual blog post in order to retrieve its data, so let's create a `$path` argument. We'll set it to a `String` data type and require it be passed in. 358 | 359 | ```jsx 360 | query BlogPost($path:String!){ 361 | markdownRemark (frontmatter: { path: { eq: $path } }){ 362 | frontmatter { 363 | date 364 | description 365 | path 366 | title 367 | } 368 | html 369 | id 370 | } 371 | } 372 | ``` 373 | 374 | 16. Let's test if this works by adding a query variable. 375 | 376 | At the bottom of GraphiQL there is a Query Variables text editor. Click to open it, and add the following: 377 | 378 | ```jsx 379 | { 380 | "path": "/five-tech-skills-to-master" 381 | } 382 | ``` 383 | 384 | 17. To test that this works, click the play button. You should see the "Five tech skills to master" blog post data populate the right side of GraphiQL. 385 | 386 | ![Five tech skills data](https://user-images.githubusercontent.com/7671983/63642544-a7064980-c6b8-11e9-8232-95da7087e86d.png) 387 | 388 | 18. Now let's use our data to build individual blog pages. 389 | 390 | First, let's create a template which will be used to dynamically render each blog when the "Read more" link is clicked. 391 | 392 | Open the `blogPost.js` file located in the `templates/` directory. 393 | 394 | 19. Inside of the `postQuery` declaration, paste our newly-created GraphQL query: 395 | ```jsx 396 | export const postQuery = graphql` 397 | query BlogPost($path: String!) { 398 | markdownRemark(frontmatter: { path: { eq: $path } }) { 399 | html 400 | frontmatter { 401 | date 402 | path 403 | title 404 | } 405 | } 406 | } 407 | ` 408 | ``` 409 | 410 | 20. Now we can pass `data` from the GraphQL query as a de-structured prop to the template and render the data. 411 | ``` 412 | export default function Template({ data }) { 413 | 414 | } 415 | ``` 416 | 417 | We'll save `data.markdownRemark` as a constant called `post` to make our rendering a little nicer. 418 | 419 | ```jsx 420 | const post = data.markdownRemark 421 | ``` 422 | 423 | 21. Finally, let's add the following JSX elements: 424 | - A `

    ` containing the post title 425 | - An `

    ` containing the author and date 426 | - A `
    ` containing the HTML for the post. 427 | 428 | To render the HTML, we'll use Gatsby's `dangerouslySetInnerHTML` and pass in `post.html`: 429 | 430 | ```jsx 431 |
    432 | ``` 433 | 434 | We will wrap all of the blog post JSX inside of the `` component. 435 | 436 | Here is the completed `blogPost.js` template: 437 | 438 | ```jsx 439 | 440 | import React from 'react' 441 | import { Link, graphql } from 'gatsby' 442 | 443 | import Layout from '../components/layout' 444 | 445 | export default function Template({ data }) { 446 | const post = data.markdownRemark 447 | 448 | return ( 449 | 450 | 461 | Back to blogs 462 | 463 |

    468 | {post.frontmatter.title} 469 |

    470 |

    477 | Posted by {post.frontmatter.author} on {post.frontmatter.date} 478 |

    479 |
    480 | 481 | ) 482 | } 483 | 484 | export const postQuery = graphql` 485 | query BlogPost($path: String!) { 486 | markdownRemark(frontmatter: { path: { eq: $path } }) { 487 | html 488 | frontmatter { 489 | date 490 | path 491 | title 492 | } 493 | } 494 | } 495 | ` 496 | ``` 497 | 498 | 22. There's one last thing we have to add to get our blog posts to render. 499 | 500 | We don't want to create individual pages for each blog post; that would be extremely time consuming! 501 | 502 | Instead, we want to use GraphQL to dynamically create each page. 503 | 504 | We can do this within `gatsby-node.js`. 505 | 506 | - Inside of `gatsby-node.js`, import `path`, a [node module](https://nodejs.org/api/path.html) for working with our file directories. 507 | 508 | ```jsx 509 | const path = require('path') 510 | ``` 511 | 512 | - Let's use the node `exports.createPages` [API](https://www.gatsbyjs.org/docs/node-apis/) to dynamically generate our pages. 513 | 514 | I won't delve into the details of the `createPages` API in this post, but you can check out the documentation linked above. 515 | 516 | Add the follwing code underneath the `path` module import: 517 | 518 | ```jsx 519 | exports.createPages = ({ boundActionCreators, graphql }) => { 520 | const { createPage } = boundActionCreators 521 | 522 | const postTemplate = path.resolve('src/templates/blogPost.js') 523 | } 524 | ``` 525 | 526 | - Now we want to return a GraphQL query to grab all of the blog posts: 527 | 528 | ```jsx 529 | return graphql(` 530 | { 531 | allMarkdownRemark { 532 | edges { 533 | node { 534 | html 535 | id 536 | frontmatter { 537 | path 538 | title 539 | date 540 | author 541 | } 542 | } 543 | } 544 | } 545 | } 546 | `) 547 | ``` 548 | 549 | - Once we receive a response back from the query, we want to reject the promise if an error occurred, and otherwise create a page for each post. 550 | 551 | This will create a post at the designated path received from the query results, and will use the `postTemplate` we declared above (our `blogPost.js` template) to render each post. 552 | 553 | ```jsx 554 | ... 555 | `).then(res => { 556 | if (res.errors) { 557 | return Promise.reject(res.errors) 558 | } 559 | 560 | res.data.allMarkdownRemark.edges.forEach(({ node }) => { 561 | createPage({ 562 | path: node.frontmatter.path, 563 | component: postTemplate, 564 | }) 565 | }) 566 | ``` 567 | 568 | Here's the finalized code for `gatsby-node.js`: 569 | 570 | ```jsx 571 | const path = require('path') 572 | 573 | exports.createPages = ({ boundActionCreators, graphql }) => { 574 | const { createPage } = boundActionCreators 575 | 576 | const postTemplate = path.resolve('src/templates/blogPost.js') 577 | 578 | return graphql(` 579 | { 580 | allMarkdownRemark { 581 | edges { 582 | node { 583 | html 584 | id 585 | frontmatter { 586 | path 587 | title 588 | date 589 | author 590 | } 591 | } 592 | } 593 | } 594 | } 595 | `).then(res => { 596 | if (res.errors) { 597 | return Promise.reject(res.errors) 598 | } 599 | 600 | res.data.allMarkdownRemark.edges.forEach(({ node }) => { 601 | createPage({ 602 | path: node.frontmatter.path, 603 | component: postTemplate, 604 | }) 605 | }) 606 | }) 607 | } 608 | ``` 609 | 610 | Now we're ready to see if it worked! 611 | 612 | 23. Re-start your development server, then head over to the browser and click one of the blog post "Read more" links: 613 | 614 | ![Blog post](https://user-images.githubusercontent.com/7671983/63642557-dae16f00-c6b8-11e9-8cdf-dd7ed92307a6.png) 615 | 616 | _If you ever need to revert to this state, you can use `git checkout step-3` to check out the tag for this state._ 617 | 618 | 619 | 620 | # Step 4: Adding Custom Google Fonts 621 | You may want to add some custom [Google fonts](https://fonts.google.com/) to your Gatsby site. Luckily there's a plug-in for that! 622 | 623 | 1. In the terminal run the following command to install the Google fonts plugin: 624 | ``` 625 | yarn add gatsby-plugin-google-fonts 626 | ``` 627 | 628 | 2. Inside `gatsby-config.js`, we need to configure the plug-in. 629 | 630 | I'm choosing to use the font PT Serif, but you can select whichever font you prefer. 631 | 632 | Add the following to the `plugins` array: 633 | 634 | ```jsx 635 | plugins: [ 636 | ... 637 | { 638 | resolve: `gatsby-plugin-google-fonts`, 639 | options: { 640 | fonts: [ 641 | `PT Serif`, 642 | ], 643 | display: 'swap' 644 | } 645 | }, 646 | ] 647 | ``` 648 | 649 | You can check out the full list of options for this plugin in the [Gatsby docs](https://www.gatsbyjs.org/packages/gatsby-plugin-google-fonts/). 650 | 651 | 3. Inside of `nav.css` update the `.nav__list` selector to use `font-family: 'PT Serif', sans-serif`; 652 | 653 | 4. Restart your development server to see the updated font family. 654 | 655 | ![New font](https://user-images.githubusercontent.com/7671983/63642694-2006a080-c6bb-11e9-88a5-3f66138cb06f.png) 656 | 657 | _If you ever need to revert to this state, you can use `git checkout step-4` to check out the tag for this state._ 658 | 659 | 660 | # Step 5: Deploying To Netlify 661 | Gatsby makes it extremely easy to deploy your site using Netlify. 662 | 663 | 1. Head over to [Netlify](https://app.netlify.com/) and sign up for an account. 664 | 2. Click "New site from Git". 665 | 3. Select "GitHub" for continuous deployment, authenticate with your GitHub account, and select the repository. 666 | 4. Set your build options (I leave the defaults). 667 | 5. Click "Deploy site". 668 | 669 | ![Building](https://user-images.githubusercontent.com/7671983/63642758-4416b180-c6bc-11e9-8015-ca86f65c7dce.png) 670 | 671 | Now, every time I push to `master`, my site will deploy. 672 | 673 | Once your site deploys, head over to the URL to check it out! You can easily add [custom domains](https://www.netlify.com/docs/custom-domains/) with Netlify to personalize your portfolio. 674 | 675 | 676 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Browser APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/browser-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: `My Blog`, 4 | description: `This is my blog`, 5 | author: `@me`, 6 | }, 7 | plugins: [ 8 | `gatsby-plugin-react-helmet`, 9 | `gatsby-transformer-sharp`, 10 | `gatsby-transformer-remark`, 11 | `gatsby-plugin-sharp`, 12 | { 13 | resolve: 'gatsby-source-filesystem', 14 | options: { 15 | path: `${__dirname}/src/pages`, 16 | name: 'pages', 17 | }, 18 | }, 19 | { 20 | resolve: `gatsby-plugin-google-fonts`, 21 | options: { 22 | fonts: [ 23 | `PT Serif`, 24 | ], 25 | display: 'swap' 26 | } 27 | }, 28 | { 29 | resolve: `gatsby-plugin-manifest`, 30 | options: { 31 | name: `gatsby-starter-default`, 32 | short_name: `starter`, 33 | start_url: `/` 34 | }, 35 | }, 36 | ], 37 | } -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | exports.createPages = ({ boundActionCreators, graphql }) => { 4 | const { createPage } = boundActionCreators 5 | 6 | const postTemplate = path.resolve('src/templates/blogPost.js') 7 | 8 | return graphql(` 9 | { 10 | allMarkdownRemark { 11 | edges { 12 | node { 13 | html 14 | id 15 | frontmatter { 16 | path 17 | title 18 | date 19 | author 20 | } 21 | } 22 | } 23 | } 24 | } 25 | `).then(res => { 26 | if (res.errors) { 27 | return Promise.reject(res.errors) 28 | } 29 | 30 | res.data.allMarkdownRemark.edges.forEach(({ node }) => { 31 | createPage({ 32 | path: node.frontmatter.path, 33 | component: postTemplate, 34 | }) 35 | }) 36 | }) 37 | } -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-starter-default", 3 | "private": true, 4 | "description": "A simple starter to get up and developing quickly with Gatsby", 5 | "version": "0.1.0", 6 | "author": "Kyle Mathews ", 7 | "dependencies": { 8 | "gatsby": "^2.13.73", 9 | "gatsby-image": "^2.2.10", 10 | "gatsby-plugin-google-fonts": "^1.0.1", 11 | "gatsby-plugin-manifest": "^2.2.6", 12 | "gatsby-plugin-offline": "^2.2.7", 13 | "gatsby-plugin-react-helmet": "^3.1.4", 14 | "gatsby-plugin-sharp": "^2.2.13", 15 | "gatsby-source-filesystem": "^2.1.11", 16 | "gatsby-transformer-remark": "^2.6.19", 17 | "gatsby-transformer-sharp": "^2.2.7", 18 | "prop-types": "^15.7.2", 19 | "react": "^16.9.0", 20 | "react-dom": "^16.9.0", 21 | "react-helmet": "^5.2.1" 22 | }, 23 | "devDependencies": { 24 | "prettier": "^1.18.2" 25 | }, 26 | "keywords": [ 27 | "gatsby" 28 | ], 29 | "license": "MIT", 30 | "scripts": { 31 | "build": "gatsby build", 32 | "develop": "gatsby develop", 33 | "format": "prettier --write \"**/*.{js,jsx,json,md}\"", 34 | "start": "npm run develop", 35 | "serve": "gatsby serve", 36 | "test": "echo \"Write tests! -> https://gatsby.dev/unit-testing \"" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/gatsbyjs/gatsby-starter-default" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/gatsbyjs/gatsby/issues" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /queries.md: -------------------------------------------------------------------------------- 1 | # All Blog Posts 2 | ## writing.js 3 | 4 | ``` 5 | export const allBlogsQuery = graphql` 6 | query AllBlogPosts { 7 | allMarkdownRemark { 8 | edges { 9 | node { 10 | frontmatter { 11 | author 12 | date 13 | title 14 | path 15 | description 16 | } 17 | id 18 | } 19 | } 20 | } 21 | } 22 | `; 23 | ``` 24 | 25 | # Single Blog Post 26 | # templates/blogPost.js 27 | 28 | ``` 29 | export const postQuery = graphql` 30 | query BlogPost($path: String!) { 31 | markdownRemark(frontmatter: { path: { eq: $path } }) { 32 | html 33 | frontmatter { 34 | date 35 | path 36 | title 37 | } 38 | } 39 | } 40 | ` 41 | ``` 42 | 43 | # Dynamically Generate 44 | ## gatsby-node.js 45 | 46 | ``` 47 | const path = require('path') 48 | 49 | exports.createPages = ({ boundActionCreators, graphql }) => { 50 | const { createPage } = boundActionCreators 51 | 52 | const postTemplate = path.resolve('src/templates/blogPost.js') 53 | 54 | return graphql(` 55 | { 56 | allMarkdownRemark { 57 | edges { 58 | node { 59 | html 60 | id 61 | frontmatter { 62 | path 63 | title 64 | date 65 | author 66 | } 67 | } 68 | } 69 | } 70 | } 71 | `).then(res => { 72 | if (res.errors) { 73 | return Promise.reject(res.errors) 74 | } 75 | 76 | res.data.allMarkdownRemark.edges.forEach(({ node }) => { 77 | createPage({ 78 | path: node.frontmatter.path, 79 | component: postTemplate, 80 | }) 81 | }) 82 | }) 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /src/components/blogSquare.css: -------------------------------------------------------------------------------- 1 | .blogSquare { 2 | position: relative; 3 | display: flex; 4 | margin-bottom: 50px; 5 | flex-direction: column; 6 | padding-bottom: 50px; 7 | border-bottom: 1px solid #efefef; 8 | } 9 | 10 | .blogSquare__link { 11 | text-decoration: none; 12 | color: #4a4a4a; 13 | } 14 | 15 | .blogSquare__readMore { 16 | font-size: .8em; 17 | text-decoration: underline; 18 | } 19 | 20 | .blogSquare__title { 21 | font-weight: 200; 22 | margin: 0; 23 | font-size: 1.5em; 24 | } 25 | 26 | .blogSquare__date { 27 | margin: 5px 0px 15px; 28 | font-size: 0.8em; 29 | font-weight: bold; 30 | } 31 | 32 | .blogSquare__readMore { 33 | font-size: 0.8em; 34 | display: inline-block; 35 | } -------------------------------------------------------------------------------- /src/components/blogSquare.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Link } from "gatsby"; 4 | import './blogSquare.css' 5 | 6 | const BlogSquare = ({ title, date, path, description }) => ( 7 |
    8 | 12 |

    {title}

    13 |

    {date}

    14 |

    {description}

    15 |

    Read more

    16 | 17 |
    18 | ) 19 | 20 | export default BlogSquare -------------------------------------------------------------------------------- /src/components/layout.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | body { 7 | margin: 0; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | main, 19 | menu, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | audio, 26 | canvas, 27 | progress, 28 | video { 29 | display: inline-block; 30 | } 31 | audio:not([controls]) { 32 | display: none; 33 | height: 0; 34 | } 35 | progress { 36 | vertical-align: baseline; 37 | } 38 | [hidden], 39 | template { 40 | display: none; 41 | } 42 | a { 43 | background-color: transparent; 44 | -webkit-text-decoration-skip: objects; 45 | } 46 | a:active, 47 | a:hover { 48 | outline-width: 0; 49 | } 50 | abbr[title] { 51 | border-bottom: none; 52 | text-decoration: underline; 53 | text-decoration: underline dotted; 54 | } 55 | b, 56 | strong { 57 | font-weight: inherit; 58 | font-weight: bolder; 59 | } 60 | dfn { 61 | font-style: italic; 62 | } 63 | h1 { 64 | font-size: 2em; 65 | margin: 0.67em 0; 66 | } 67 | mark { 68 | background-color: #ff0; 69 | color: #000; 70 | } 71 | small { 72 | font-size: 80%; 73 | } 74 | sub, 75 | sup { 76 | font-size: 75%; 77 | line-height: 0; 78 | position: relative; 79 | vertical-align: baseline; 80 | } 81 | sub { 82 | bottom: -0.25em; 83 | } 84 | sup { 85 | top: -0.5em; 86 | } 87 | img { 88 | border-style: none; 89 | } 90 | svg:not(:root) { 91 | overflow: hidden; 92 | } 93 | code, 94 | kbd, 95 | pre, 96 | samp { 97 | font-family: monospace, monospace; 98 | font-size: 1em; 99 | } 100 | figure { 101 | margin: 1em 40px; 102 | } 103 | hr { 104 | box-sizing: content-box; 105 | height: 0; 106 | overflow: visible; 107 | } 108 | button, 109 | input, 110 | optgroup, 111 | select, 112 | textarea { 113 | font: inherit; 114 | margin: 0; 115 | } 116 | optgroup { 117 | font-weight: 700; 118 | } 119 | button, 120 | input { 121 | overflow: visible; 122 | } 123 | button, 124 | select { 125 | text-transform: none; 126 | } 127 | [type="reset"], 128 | [type="submit"], 129 | button, 130 | html [type="button"] { 131 | -webkit-appearance: button; 132 | } 133 | [type="button"]::-moz-focus-inner, 134 | [type="reset"]::-moz-focus-inner, 135 | [type="submit"]::-moz-focus-inner, 136 | button::-moz-focus-inner { 137 | border-style: none; 138 | padding: 0; 139 | } 140 | [type="button"]:-moz-focusring, 141 | [type="reset"]:-moz-focusring, 142 | [type="submit"]:-moz-focusring, 143 | button:-moz-focusring { 144 | outline: 1px dotted ButtonText; 145 | } 146 | fieldset { 147 | border: 1px solid silver; 148 | margin: 0 2px; 149 | padding: 0.35em 0.625em 0.75em; 150 | } 151 | legend { 152 | box-sizing: border-box; 153 | color: inherit; 154 | display: table; 155 | max-width: 100%; 156 | padding: 0; 157 | white-space: normal; 158 | } 159 | textarea { 160 | overflow: auto; 161 | } 162 | [type="checkbox"], 163 | [type="radio"] { 164 | box-sizing: border-box; 165 | padding: 0; 166 | } 167 | [type="number"]::-webkit-inner-spin-button, 168 | [type="number"]::-webkit-outer-spin-button { 169 | height: auto; 170 | } 171 | [type="search"] { 172 | -webkit-appearance: textfield; 173 | outline-offset: -2px; 174 | } 175 | [type="search"]::-webkit-search-cancel-button, 176 | [type="search"]::-webkit-search-decoration { 177 | -webkit-appearance: none; 178 | } 179 | ::-webkit-input-placeholder { 180 | color: inherit; 181 | opacity: 0.54; 182 | } 183 | ::-webkit-file-upload-button { 184 | -webkit-appearance: button; 185 | font: inherit; 186 | } 187 | html { 188 | font: 112.5%/1.45em georgia, serif; 189 | box-sizing: border-box; 190 | overflow-y: scroll; 191 | } 192 | * { 193 | box-sizing: inherit; 194 | } 195 | *:before { 196 | box-sizing: inherit; 197 | } 198 | *:after { 199 | box-sizing: inherit; 200 | } 201 | body { 202 | color: hsla(0, 0%, 0%, 0.8); 203 | font-family: georgia, serif; 204 | font-weight: normal; 205 | word-wrap: break-word; 206 | font-kerning: normal; 207 | -moz-font-feature-settings: "kern", "liga", "clig", "calt"; 208 | -ms-font-feature-settings: "kern", "liga", "clig", "calt"; 209 | -webkit-font-feature-settings: "kern", "liga", "clig", "calt"; 210 | font-feature-settings: "kern", "liga", "clig", "calt"; 211 | } 212 | img { 213 | max-width: 100%; 214 | margin-left: 0; 215 | margin-right: 0; 216 | margin-top: 0; 217 | padding-bottom: 0; 218 | padding-left: 0; 219 | padding-right: 0; 220 | padding-top: 0; 221 | margin-bottom: 1.45rem; 222 | } 223 | h1 { 224 | margin-left: 0; 225 | margin-right: 0; 226 | margin-top: 0; 227 | padding-bottom: 0; 228 | padding-left: 0; 229 | padding-right: 0; 230 | padding-top: 0; 231 | margin-bottom: 1.45rem; 232 | color: inherit; 233 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 234 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 235 | font-weight: bold; 236 | text-rendering: optimizeLegibility; 237 | font-size: 2.25rem; 238 | line-height: 1.1; 239 | } 240 | h2 { 241 | margin-left: 0; 242 | margin-right: 0; 243 | margin-top: 0; 244 | padding-bottom: 0; 245 | padding-left: 0; 246 | padding-right: 0; 247 | padding-top: 0; 248 | margin-bottom: 1.45rem; 249 | color: inherit; 250 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 251 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 252 | font-weight: bold; 253 | text-rendering: optimizeLegibility; 254 | font-size: 1.62671rem; 255 | line-height: 1.1; 256 | } 257 | h3 { 258 | margin-left: 0; 259 | margin-right: 0; 260 | margin-top: 0; 261 | padding-bottom: 0; 262 | padding-left: 0; 263 | padding-right: 0; 264 | padding-top: 0; 265 | margin-bottom: 1.45rem; 266 | color: inherit; 267 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 268 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 269 | font-weight: bold; 270 | text-rendering: optimizeLegibility; 271 | font-size: 1.38316rem; 272 | line-height: 1.1; 273 | } 274 | h4 { 275 | margin-left: 0; 276 | margin-right: 0; 277 | margin-top: 0; 278 | padding-bottom: 0; 279 | padding-left: 0; 280 | padding-right: 0; 281 | padding-top: 0; 282 | margin-bottom: 1.45rem; 283 | color: inherit; 284 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 285 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 286 | font-weight: bold; 287 | text-rendering: optimizeLegibility; 288 | font-size: 1rem; 289 | line-height: 1.1; 290 | } 291 | h5 { 292 | margin-left: 0; 293 | margin-right: 0; 294 | margin-top: 0; 295 | padding-bottom: 0; 296 | padding-left: 0; 297 | padding-right: 0; 298 | padding-top: 0; 299 | margin-bottom: 1.45rem; 300 | color: inherit; 301 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 302 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 303 | font-weight: bold; 304 | text-rendering: optimizeLegibility; 305 | font-size: 0.85028rem; 306 | line-height: 1.1; 307 | } 308 | h6 { 309 | margin-left: 0; 310 | margin-right: 0; 311 | margin-top: 0; 312 | padding-bottom: 0; 313 | padding-left: 0; 314 | padding-right: 0; 315 | padding-top: 0; 316 | margin-bottom: 1.45rem; 317 | color: inherit; 318 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 319 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 320 | font-weight: bold; 321 | text-rendering: optimizeLegibility; 322 | font-size: 0.78405rem; 323 | line-height: 1.1; 324 | } 325 | hgroup { 326 | margin-left: 0; 327 | margin-right: 0; 328 | margin-top: 0; 329 | padding-bottom: 0; 330 | padding-left: 0; 331 | padding-right: 0; 332 | padding-top: 0; 333 | margin-bottom: 1.45rem; 334 | } 335 | ul { 336 | margin-left: 1.45rem; 337 | margin-right: 0; 338 | margin-top: 0; 339 | padding-bottom: 0; 340 | padding-left: 0; 341 | padding-right: 0; 342 | padding-top: 0; 343 | margin-bottom: 1.45rem; 344 | list-style-position: outside; 345 | list-style-image: none; 346 | } 347 | ol { 348 | margin-left: 1.45rem; 349 | margin-right: 0; 350 | margin-top: 0; 351 | padding-bottom: 0; 352 | padding-left: 0; 353 | padding-right: 0; 354 | padding-top: 0; 355 | margin-bottom: 1.45rem; 356 | list-style-position: outside; 357 | list-style-image: none; 358 | } 359 | dl { 360 | margin-left: 0; 361 | margin-right: 0; 362 | margin-top: 0; 363 | padding-bottom: 0; 364 | padding-left: 0; 365 | padding-right: 0; 366 | padding-top: 0; 367 | margin-bottom: 1.45rem; 368 | } 369 | dd { 370 | margin-left: 0; 371 | margin-right: 0; 372 | margin-top: 0; 373 | padding-bottom: 0; 374 | padding-left: 0; 375 | padding-right: 0; 376 | padding-top: 0; 377 | margin-bottom: 1.45rem; 378 | } 379 | p { 380 | margin-left: 0; 381 | margin-right: 0; 382 | margin-top: 0; 383 | padding-bottom: 0; 384 | padding-left: 0; 385 | padding-right: 0; 386 | padding-top: 0; 387 | margin-bottom: 1.45rem; 388 | } 389 | figure { 390 | margin-left: 0; 391 | margin-right: 0; 392 | margin-top: 0; 393 | padding-bottom: 0; 394 | padding-left: 0; 395 | padding-right: 0; 396 | padding-top: 0; 397 | margin-bottom: 1.45rem; 398 | } 399 | pre { 400 | margin-left: 0; 401 | margin-right: 0; 402 | margin-top: 0; 403 | margin-bottom: 1.45rem; 404 | font-size: 0.85rem; 405 | line-height: 1.42; 406 | background: hsla(0, 0%, 0%, 0.04); 407 | border-radius: 3px; 408 | overflow: auto; 409 | word-wrap: normal; 410 | padding: 1.45rem; 411 | } 412 | table { 413 | margin-left: 0; 414 | margin-right: 0; 415 | margin-top: 0; 416 | padding-bottom: 0; 417 | padding-left: 0; 418 | padding-right: 0; 419 | padding-top: 0; 420 | margin-bottom: 1.45rem; 421 | font-size: 1rem; 422 | line-height: 1.45rem; 423 | border-collapse: collapse; 424 | width: 100%; 425 | } 426 | fieldset { 427 | margin-left: 0; 428 | margin-right: 0; 429 | margin-top: 0; 430 | padding-bottom: 0; 431 | padding-left: 0; 432 | padding-right: 0; 433 | padding-top: 0; 434 | margin-bottom: 1.45rem; 435 | } 436 | blockquote { 437 | margin-left: 1.45rem; 438 | margin-right: 1.45rem; 439 | margin-top: 0; 440 | padding-bottom: 0; 441 | padding-left: 0; 442 | padding-right: 0; 443 | padding-top: 0; 444 | margin-bottom: 1.45rem; 445 | } 446 | form { 447 | margin-left: 0; 448 | margin-right: 0; 449 | margin-top: 0; 450 | padding-bottom: 0; 451 | padding-left: 0; 452 | padding-right: 0; 453 | padding-top: 0; 454 | margin-bottom: 1.45rem; 455 | } 456 | noscript { 457 | margin-left: 0; 458 | margin-right: 0; 459 | margin-top: 0; 460 | padding-bottom: 0; 461 | padding-left: 0; 462 | padding-right: 0; 463 | padding-top: 0; 464 | margin-bottom: 1.45rem; 465 | } 466 | iframe { 467 | margin-left: 0; 468 | margin-right: 0; 469 | margin-top: 0; 470 | padding-bottom: 0; 471 | padding-left: 0; 472 | padding-right: 0; 473 | padding-top: 0; 474 | margin-bottom: 1.45rem; 475 | } 476 | hr { 477 | margin-left: 0; 478 | margin-right: 0; 479 | margin-top: 0; 480 | padding-bottom: 0; 481 | padding-left: 0; 482 | padding-right: 0; 483 | padding-top: 0; 484 | margin-bottom: calc(1.45rem - 1px); 485 | background: hsla(0, 0%, 0%, 0.2); 486 | border: none; 487 | height: 1px; 488 | } 489 | address { 490 | margin-left: 0; 491 | margin-right: 0; 492 | margin-top: 0; 493 | padding-bottom: 0; 494 | padding-left: 0; 495 | padding-right: 0; 496 | padding-top: 0; 497 | margin-bottom: 1.45rem; 498 | } 499 | b { 500 | font-weight: bold; 501 | } 502 | strong { 503 | font-weight: bold; 504 | } 505 | dt { 506 | font-weight: bold; 507 | } 508 | th { 509 | font-weight: bold; 510 | } 511 | li { 512 | margin-bottom: calc(1.45rem / 2); 513 | } 514 | ol li { 515 | padding-left: 0; 516 | } 517 | ul li { 518 | padding-left: 0; 519 | } 520 | li > ol { 521 | margin-left: 1.45rem; 522 | margin-bottom: calc(1.45rem / 2); 523 | margin-top: calc(1.45rem / 2); 524 | } 525 | li > ul { 526 | margin-left: 1.45rem; 527 | margin-bottom: calc(1.45rem / 2); 528 | margin-top: calc(1.45rem / 2); 529 | } 530 | blockquote *:last-child { 531 | margin-bottom: 0; 532 | } 533 | li *:last-child { 534 | margin-bottom: 0; 535 | } 536 | p *:last-child { 537 | margin-bottom: 0; 538 | } 539 | li > p { 540 | margin-bottom: calc(1.45rem / 2); 541 | } 542 | code { 543 | font-size: 0.85rem; 544 | line-height: 1.45rem; 545 | } 546 | kbd { 547 | font-size: 0.85rem; 548 | line-height: 1.45rem; 549 | } 550 | samp { 551 | font-size: 0.85rem; 552 | line-height: 1.45rem; 553 | } 554 | abbr { 555 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 556 | cursor: help; 557 | } 558 | acronym { 559 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 560 | cursor: help; 561 | } 562 | abbr[title] { 563 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 564 | cursor: help; 565 | text-decoration: none; 566 | } 567 | thead { 568 | text-align: left; 569 | } 570 | td, 571 | th { 572 | text-align: left; 573 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); 574 | font-feature-settings: "tnum"; 575 | -moz-font-feature-settings: "tnum"; 576 | -ms-font-feature-settings: "tnum"; 577 | -webkit-font-feature-settings: "tnum"; 578 | padding-left: 0.96667rem; 579 | padding-right: 0.96667rem; 580 | padding-top: 0.725rem; 581 | padding-bottom: calc(0.725rem - 1px); 582 | } 583 | th:first-child, 584 | td:first-child { 585 | padding-left: 0; 586 | } 587 | th:last-child, 588 | td:last-child { 589 | padding-right: 0; 590 | } 591 | tt, 592 | code { 593 | background-color: hsla(0, 0%, 0%, 0.04); 594 | border-radius: 3px; 595 | font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono", 596 | "Liberation Mono", Menlo, Courier, monospace; 597 | padding: 0; 598 | padding-top: 0.2em; 599 | padding-bottom: 0.2em; 600 | } 601 | pre code { 602 | background: none; 603 | line-height: 1.42; 604 | } 605 | code:before, 606 | code:after, 607 | tt:before, 608 | tt:after { 609 | letter-spacing: -0.2em; 610 | content: " "; 611 | } 612 | pre code:before, 613 | pre code:after, 614 | pre tt:before, 615 | pre tt:after { 616 | content: ""; 617 | } 618 | @media only screen and (max-width: 480px) { 619 | html { 620 | font-size: 100%; 621 | } 622 | } 623 | 624 | .layout { 625 | display: flex; 626 | } 627 | 628 | .main { 629 | width: 100%; 630 | height: 100vh; 631 | display: flex; 632 | flex-direction: column; 633 | padding: 80px 120px; 634 | } -------------------------------------------------------------------------------- /src/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import PropTypes from "prop-types" 3 | import { useStaticQuery, graphql } from "gatsby" 4 | 5 | import Nav from "../components/nav"; 6 | 7 | import "./layout.css" 8 | 9 | const Layout = ({ children }) => { 10 | const data = useStaticQuery(graphql` 11 | query SiteTitleQuery { 12 | site { 13 | siteMetadata { 14 | title 15 | } 16 | } 17 | } 18 | `) 19 | 20 | return ( 21 |
    22 |
    25 | ) 26 | } 27 | 28 | Layout.propTypes = { 29 | children: PropTypes.node.isRequired, 30 | } 31 | 32 | export default Layout -------------------------------------------------------------------------------- /src/components/nav.css: -------------------------------------------------------------------------------- 1 | .nav { 2 | padding: 24px; 3 | width: 300px; 4 | } 5 | 6 | .nav__list { 7 | list-style: none; 8 | padding: 0; 9 | margin: 0; 10 | font-family: 'PT Serif', serif; 11 | } 12 | 13 | .nav__link { 14 | text-decoration: none; 15 | color: #4A4A4A; 16 | } 17 | 18 | .nav__link--active { 19 | font-weight: bold; 20 | } -------------------------------------------------------------------------------- /src/components/nav.js: -------------------------------------------------------------------------------- 1 | 2 | import React from "react"; 3 | import { Link } from "gatsby"; 4 | 5 | import "./nav.css"; 6 | 7 | const Nav = () => ( 8 | 17 | ) 18 | 19 | export default Nav; -------------------------------------------------------------------------------- /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/2019-08-24-cute-cats/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: '/cute-cats' 3 | date: '2019-08-24' 4 | title: 'Cute Cats' 5 | author: 'Me' 6 | description: 'Here are some cute cat photos!' 7 | --- 8 | ![Cat under a blanket](https://images.unsplash.com/photo-1494256997604-768d1f608cac?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=700&q=60) 9 | 10 | -------------------------------------------------------------------------------- /src/pages/2019-08-26-five-tech-skills-to-master/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: '/five-tech-skills-to-master' 3 | date: '2019-08-26' 4 | title: 'Five Tech Skills To Master' 5 | author: 'Bob Ross' 6 | description: 'Here are the five tech skills you MUST master to get a job.' 7 | --- 8 | 9 | ![Headphones on a table](https://images.unsplash.com/photo-1498049794561-7780e7231661?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80) 10 | 11 | -------------------------------------------------------------------------------- /src/pages/2019-09-06-hello-graphql-day-bodensee/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: '/hello-graphql-day-bodensee' 3 | date: '2019-09-06' 4 | title: 'Hello GraphQL Day Bodensee' 5 | author: 'Emma Wedekind' 6 | description: 'We learning things about GraphQL! (hopefully).' 7 | --- 8 | 9 | ![DONT TOUCH MY GARBAGE](https://user-images.githubusercontent.com/7671983/64414434-a0ea7280-d093-11e9-882d-63b66087129b.png) 10 | 11 | -------------------------------------------------------------------------------- /src/pages/2019-09-13-hey-amsterdam/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: '/hey-amsterdam' 3 | date: '2019-09-13' 4 | title: 'Hello React Live' 5 | author: 'Emma Wedekind' 6 | description: 'We learning things about REACT! (hopefully).' 7 | --- 8 | 9 | # YAY 10 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import Layout from "../components/layout" 4 | import SEO from "../components/seo" 5 | 6 | const NotFoundPage = () => ( 7 | 8 | 9 |

    NOT FOUND

    10 |

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

    11 |
    12 | ) 13 | 14 | export default NotFoundPage 15 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link } from "gatsby" 3 | import Layout from "../components/layout" 4 | 5 | const IndexPage = () => ( 6 | 7 |

    This is my portfolio.

    8 |

    I build cool things.

    9 |
    10 | ) 11 | 12 | export default IndexPage 13 | -------------------------------------------------------------------------------- /src/pages/podcasting.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link } from "gatsby" 3 | 4 | import Layout from "../components/layout" 5 | 6 | import SEO from "../components/seo" 7 | 8 | const PodcastingPage = () => ( 9 | 10 | 11 |

    I podcast about things

    12 |
    13 | ) 14 | 15 | export default PodcastingPage -------------------------------------------------------------------------------- /src/pages/speaking.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link } from "gatsby" 3 | 4 | import Layout from "../components/layout" 5 | 6 | import SEO from "../components/seo" 7 | 8 | const SpeakingPage = () => ( 9 | 10 | 11 |

    I speak about things

    12 |
    13 | ) 14 | 15 | export default SpeakingPage -------------------------------------------------------------------------------- /src/pages/writing.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import Layout from "../components/layout" 4 | import BlogSquare from "../components/blogSquare" 5 | 6 | import SEO from "../components/seo" 7 | 8 | const WritingPage = ({ data }) => ( 9 | 10 | 11 |

    I write about things

    12 | {data.allMarkdownRemark.edges.map(post => ( 13 | 20 | ))} 21 |
    22 | ) 23 | 24 | export const allBlogsQuery = graphql` 25 | query AllBlogPosts { 26 | allMarkdownRemark { 27 | edges { 28 | node { 29 | frontmatter { 30 | date 31 | description 32 | title 33 | path 34 | } 35 | id 36 | } 37 | } 38 | } 39 | } 40 | ` 41 | 42 | export default WritingPage -------------------------------------------------------------------------------- /src/templates/blogPost.css: -------------------------------------------------------------------------------- 1 | .blogPost__backToBlogs { 2 | display: flex; 3 | align-items: center; 4 | font-size: .8em; 5 | color: #4a4a4a; 6 | text-decoration: none; 7 | margin-top: 50px; 8 | } 9 | 10 | .blogPost__title { 11 | margin-top: 20px; 12 | } 13 | 14 | blogPost__postedByDate { 15 | font-size: .8em; 16 | color: #9fa7a7; 17 | font-weight: 400; 18 | } -------------------------------------------------------------------------------- /src/templates/blogPost.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link, graphql } from 'gatsby' 3 | import Layout from '../components/layout' 4 | import './blogPost.css' 5 | 6 | export default function Template({ data }) { 7 | const post = data.markdownRemark 8 | 9 | return ( 10 | 11 | 22 | Back to blogs 23 | 24 |

    29 | {post.frontmatter.title} 30 |

    31 |

    38 | Posted by {post.frontmatter.author} on {post.frontmatter.date} 39 |

    40 |
    41 | 42 | ) 43 | } 44 | 45 | export const postQuery = graphql` 46 | query BlogPost($path: String!) { 47 | markdownRemark(frontmatter: { path: { eq: $path } }) { 48 | html 49 | frontmatter { 50 | date 51 | path 52 | title 53 | } 54 | } 55 | } 56 | ` --------------------------------------------------------------------------------