<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wesley Handy</title>
    <description>The latest articles on DEV Community by Wesley Handy (@wesleylhandy).</description>
    <link>https://dev.to/wesleylhandy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F6837%2Fc9a15591-2659-458d-afe3-5dc455127119.png</url>
      <title>DEV Community: Wesley Handy</title>
      <link>https://dev.to/wesleylhandy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wesleylhandy"/>
    <language>en</language>
    <item>
      <title>New Tools, New Friends: Reflecting on JAMStackConf_SF 2019</title>
      <dc:creator>Wesley Handy</dc:creator>
      <pubDate>Mon, 21 Oct 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/wesleylhandy/new-tools-new-friends-reflecting-on-jamstackconfsf-2019-39he</link>
      <guid>https://dev.to/wesleylhandy/new-tools-new-friends-reflecting-on-jamstackconfsf-2019-39he</guid>
      <description>&lt;p&gt;&lt;a href="///static/303da88efebc2e830b4383b0143d311b/c35de/jam-stack.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G0ZW6KmN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.wesleylhandy.net/static/303da88efebc2e830b4383b0143d311b/c35de/jam-stack.jpg" alt="San Francisco JAM Stack Conference: Javascript APIs Markup." title="San Francisco JAM Stack Conference: Javascript APIs Markup."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This past week, I had the joy of attending the JAMStackConf in San Francisco with 550 other excited and curious developers. For those not familiar, yet, with the JAMStack, it stands for &lt;strong&gt;J&lt;/strong&gt; avascript &lt;strong&gt;A&lt;/strong&gt; PIs &lt;strong&gt;M&lt;/strong&gt; arkup.&lt;/p&gt;

&lt;p&gt;It's an approach to developing applications that result in highly performant static sites that consume APIs for dynamic content.&lt;/p&gt;

&lt;p&gt;Lest you be dissuaded by the term &lt;em&gt;static&lt;/em&gt;, this refers to what the browser receives not to how interactive a web application can be. In fact, Static Site Generators (SSG), like Gatsby, could be considered replacements for framework boilerplates like &lt;code&gt;create-react-app&lt;/code&gt;. Not to start an argument there, there are plenty of resources online to familiarize yourself with the JAMStack, if you are so inclined.&lt;/p&gt;

&lt;p&gt;For now, I want to offer a few reflections on my experience at the conference and the state of the JAMStack that I gathered from participating.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jamstackconf.com/sf/schedule/"&gt;Watch JAMStackConf Videos&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of the JAMStack is Dependent on Solutions That Are Being Developed and Those That Are Launching
&lt;/h2&gt;

&lt;p&gt;Some of the biggest hurdles for developers interested in developing on the JAMStack involve OAuth, Live Edits, Live Preview, CMS Solutions that are non-developer friendly and trusted, and handling builds of Large Sites, handling builds with a large volume of content production 24 hours of the day. Several of these questions received answers this week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/sgrove"&gt;Sean Grove&lt;/a&gt; of &lt;a href="https://www.onegraph.com/"&gt;OneGraph&lt;/a&gt; demoed an amazing &lt;code&gt;graphql&lt;/code&gt; product that seamlessly integrates OAuth with several popular services. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/scottgallant"&gt;Scott Gallant&lt;/a&gt; of &lt;a href="https://t.co/8nthzRUvwl?amp=1"&gt;Forestry.io&lt;/a&gt; announced the launch of &lt;code&gt;TinaCMS&lt;/code&gt; with its widget for live-editing React-based sites.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/ohadpr"&gt;Ohad Eder-Pressman&lt;/a&gt; of &lt;a href="https://www.stackbit.com/"&gt;Stackbit&lt;/a&gt; revealed their awesome tool for quickly combining Themes, SSG, and CMS for quick builds or just testing out new tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are just a few of the tools we heard about and were able to talk about later with vendors.&lt;/p&gt;

&lt;h2&gt;
  
  
  There is a Growing History of High Volume Campaigns for Major Firms Being Run On the JAMStack
&lt;/h2&gt;

&lt;p&gt;Besides showcases &lt;a href="https://www.gatsbyjs.org/showcase/"&gt;like those on Gatsby's site&lt;/a&gt;, it's great to hear from major corporations and major agencies finding the JAMStack to be a solution for many of the problems they have faced with quick turnarounds, organizing development workflows and teams, and providing high-quality performance at scale.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/justinbwatts"&gt;Justin Watts&lt;/a&gt; from Loblaw Digital shared with us how they were able to redirect a corporate culture towards greater efficiency while producing major sites used by millions of Canadians.&lt;/p&gt;

&lt;p&gt;Two of the more broadly well-known viral social movements of the past two years— &lt;strong&gt;The Kaepernick Nike Campaign&lt;/strong&gt; and &lt;strong&gt;The Chicken Sandwich Wars of 2019&lt;/strong&gt; —had behind them JAMStack solutions that were able to handle hundreds of thousands of users at a time.&lt;/p&gt;

&lt;p&gt;At the &lt;a href="https://www.rbi.com/"&gt;Restaurant Brands International&lt;/a&gt; booth I heard it said (in so many words):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The chicken may have run out, but the website surely didn't&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Netlify Function, Serverless Functions, etc., are a necessary tool to master to take full advantage of the JAMStack.
&lt;/h2&gt;

&lt;p&gt;I was fortunate to be able to attend the &lt;strong&gt;Serverless Workshop&lt;/strong&gt; led by &lt;a href="https://twitter.com/DavidWells"&gt;David Wells&lt;/a&gt;. Check out his stuff wherever you can find it (&lt;a href="https://github.com/davidwells"&gt;like here on Github&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The key to creating dynamic JAMStack applications is mastering the middle-letter of the JAM—APIs. I feel very empowered by what I learned from David. His workshop was deep where needed and broad where needed. I left with a million use-cases buzzing around my brain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there a traffic JAM ahead? While it seems there is a growing number of devs who love the JAMStack, I talked to very few who had the opportunity to work on it day-to-day in their current position or who thought it's unlikely they ever could at their current job.
&lt;/h2&gt;

&lt;p&gt;If this paragraph is all you read, this is the most important takeaway I had from the conference. I am confident in the direction of the JAMStack, the progression of the development and release of dev tooling, the quality and ease of use of the development, build and deployment process, but, but, but, and perhaps this is always the case with new tech (legacy code), I along with many other developers have little chance to use the stack without changing jobs, going freelance, or building stuff on the side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Could this create a ceiling for the JAMStack?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I, for one, intend on using it in all my side-work. But, honestly, I have a wife and three kids. I can't be coding 10-12 hours a day. I need my coding passions to line up with my employment or &lt;em&gt;fughetaboudit&lt;/em&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  That is some sweet tasting JAM
&lt;/h2&gt;

&lt;p&gt;I said the previous paragraph was the most important, it is, for the sakes of my thoughts on the stack, but this is my favorite. I really enjoyed getting to know other developers at this conference.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/bauhouse"&gt;Stephen Bau&lt;/a&gt; is a full-stack developer and UX design mentor in the Vancouver, BC, area. He is super friendly and it was a joy meeting him.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ozlop"&gt;Ozzy Lopez&lt;/a&gt; is a self-taught developer with Snap-On Diagnostics. He is a very knowledgeable developer and was a great person to spend time chatting over how to use the tools we were learning about. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/MonicaNorris1"&gt;Monica Norris&lt;/a&gt; is a Front End Developer in the Chicago area. She really impressed me as a person. She continually connected with new people and then connected them with each other. I met at least four people just because of her. She would be an asset to any team and I think would be a great team leader. Honestly, is there a ceiling for her? No ceiling at all.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/DJFalcon23"&gt;Derek Fields&lt;/a&gt; is a developer from Baltimore who knows his stuff, he's a great listener, but when he speaks, you need to listen. You can tell he has put a lot of thought in what he says.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.linkedin.com/in/amitrathik/"&gt;Amit Rathi&lt;/a&gt; we all heard from the podium. He is a Freelance Developer who is very knowledgable about Wordpress. I want to mention him because he took time out of his lunch to chat with me about some Wordpress issues I was encountering at work with the &lt;code&gt;wp-json&lt;/code&gt; REST api.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, I want to share a story with you about &lt;a href="https://twitter.com/sgrove"&gt;Sean Grove&lt;/a&gt; from OneGraph.&lt;/p&gt;

&lt;h3&gt;
  
  
  Servant Leadership Exemplified
&lt;/h3&gt;

&lt;p&gt;When I sat next to Sean over lunch, I had no idea who he was at the time. We were all wowed by his presentation not one hour later. Over lunch, he was very cordial, friendly, and engaging with everyone around the table. He didn't talk about himself, but asked us questions about who we were and what we did. So humble. That day we were served Cornish hens (with plastic forks....) and behold, my fork busted in half. Before I could do anything, without a word, Sean went and grabbed me new silverware. Then he chatted with me a bit as lunch was ending.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Why do I share this with you?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We not only need great technology to change the world, we need great people. Here is a founder of a new startup, being humble and through that showing great strength. I think we need more people like Sean and should ourselves try to emulate that type of servant leadership. Match it with some serious public speaking chops and you have someone people will follow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is also a proxy for what coding is all about.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anthropologists have long recognized that technology is simply an extension of humankind. The JAMStack, or whatever stack, company, job, or whatever, &lt;em&gt;tech is always about people&lt;/em&gt;. What we produce is intended to help others in some way, shape, form, or fashion.&lt;/p&gt;

&lt;p&gt;I've been encouraged this past week to remember people when I code. People I love, people I work with, people I meet, and really all people. We never know how far some piece of code we craft will go, nor how many others it will touch.&lt;/p&gt;

&lt;p&gt;That's humbling and yet exciting to think about, isn't it?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>jamstack</category>
      <category>conferences</category>
    </item>
    <item>
      <title>Cleaning Up Wordpress: Lessons Learned in Website Security</title>
      <dc:creator>Wesley Handy</dc:creator>
      <pubDate>Fri, 20 Sep 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/wesleylhandy/cleaning-up-wordpress-lessons-learned-in-website-security-2l2f</link>
      <guid>https://dev.to/wesleylhandy/cleaning-up-wordpress-lessons-learned-in-website-security-2l2f</guid>
      <description>&lt;p&gt;&lt;a href="/static/66c5a6070912c2b67175cdce5e6880a3/c35de/hacked.jpg"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.wesleylhandy.net%2Fstatic%2F66c5a6070912c2b67175cdce5e6880a3%2Fc35de%2Fhacked.jpg" title="Someone holding a laptop with 'You've been hacked!' displayed upon the screen" alt="Someone holding a laptop with 'You've been hacked!' displayed upon the screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You've been working on a project for months, you've handed the keys over to the client, and then you get that dreaded email...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;... on page [x] some users have been experiencing weird advertisments and popups. I think the site has been hacked! I don't know what to do. Please help! ...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I faced this situation recently. I volunteered to finish up a project for a nonprofit organization (not the one where I work 9 -5). They were one year into a new site build when the developer they originally hired ghosted them. &lt;/p&gt;

&lt;p&gt;I'm a JavaScript guy, so I wasn't all that familiar with the entirety of the &lt;code&gt;Wordpress&lt;/code&gt; ecosystem, but I figured I knew enough, I had been building my own plugin for work to inject a &lt;code&gt;React&lt;/code&gt; application via a shortcode, thinking, "How hard can it be? It's not even real development..." (don't get offended, just expressing what I was thinking at the time). &lt;/p&gt;

&lt;p&gt;I was very aware of security issues in a &lt;code&gt;Node.js&lt;/code&gt; environment, but &lt;strong&gt;&lt;em&gt;I had always relied on other services to take care of hosting security&lt;/em&gt;&lt;/strong&gt;. I had heard of &lt;code&gt;.htaccess&lt;/code&gt; files,  but I'd never put my own LAMP stack application into production from scratch. I hadn't even given it any thought since I have been focusing more and on tech like &lt;code&gt;React&lt;/code&gt;, &lt;code&gt;Gatsby&lt;/code&gt;, &lt;code&gt;React Native&lt;/code&gt;, and even a little &lt;code&gt;Angular&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;As easy (in terms of difficulty) it was to handle all the front-end requests of getting the site finished to the client's expectation, &lt;strong&gt;&lt;em&gt;I took for granted my own ignorance of the rest of what was involved.&lt;/em&gt;&lt;/strong&gt; I want to share what I have learned over the past two weeks so that (1) I won't forget next time, and (2) so you can avoid many of the mistakes I made. &lt;/p&gt;

&lt;p&gt;I am going to try and provide links to all the sources that have helped my in my recent journeys and to briefly talk through some of the more important parts. Let's call this:&lt;/p&gt;

&lt;h2&gt;
  
  
  Wordpress Security 101
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Always Hire a Developer You Either Know or Can Verifiably Trust&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is very tempting to hire the cheapest developer you can find. But sometimes you get what you pay for. &lt;/p&gt;

&lt;p&gt;I like that there are sites where developers can be rated, or public reviews left, like Upwork or LinkedIn (want to suggest others?). These help provide accountability for the developer and some sense of security for the client. In this particular case, the original developer took a low fee for a quite complex site and cut a bunch of corners. &lt;br&gt;
Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.upwork.com/" rel="noopener noreferrer"&gt;https://www.upwork.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Never download and use unlocked or &lt;code&gt;nulled&lt;/code&gt; themes - pay the regular fee from a reputable theme marketplace. Otherwise, you may install Malware!!!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't even question the use of the theme on the client's site. Knowing the client, I assumed they would have purchased the theme themselves but it turns out they left it to the developer, who perhaps out of ignorance or a willingness to cut corners downloaded a theme online that was corrupted with the &lt;code&gt;wp_vcd&lt;/code&gt; malware.&lt;/p&gt;

&lt;p&gt;I only discovered how long the malware had been there by comparing the current site with versions of the codebase that existed while the site was offline or in maintenance mode.&lt;/p&gt;

&lt;p&gt;This particular Malware was found within &lt;code&gt;functions.php&lt;/code&gt;, and it created three files within &lt;code&gt;wp-includes&lt;/code&gt;: &lt;code&gt;wp-feed.php&lt;/code&gt;, &lt;code&gt;wp-vcd.php&lt;/code&gt;, &lt;code&gt;wp-tmp.php&lt;/code&gt;. It wrapped pages with malvertising scripts and also added a script that logged user's IP addresses who had permissions to edit the site and systematically ignored those users, so they would never detect the malicious behavior when browsing the site themselves, whether logged in or not, whether browsing privately or not. &lt;/p&gt;

&lt;p&gt;After logging into the remote server as an administrator-level user, I found the odd looking files at first within the &lt;code&gt;wp-includes&lt;/code&gt; folder. I removed them, thinking I had solved the problem. The Malware was creating redirects to sites with the &lt;code&gt;oclaserver&lt;/code&gt; domain and logging IP addresses to &lt;code&gt;wp-feed.php&lt;/code&gt;. I used the following command-line script to find these files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grep -Ril "oclaserver"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;grep&lt;/code&gt; searches files for lines matching a given pattern.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;R&lt;/code&gt; makes the search recursive, looking through all files and directories within the current director&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;i&lt;/code&gt; ignores case&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;l&lt;/code&gt; suppresses the output to only list the filename&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Initially, I though this solved the problem, but the next time I checked, the files where back. I deleted the files, then checked again, immediately, the files were once again there, full of the same content. This means either my &lt;code&gt;wpdb&lt;/code&gt; was corrupted, or some other file that gets called repeatedly by Wordpress was corrupted, or possibly both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;What did I do next?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before scouring through my DB in &lt;code&gt;phpMyAdmin&lt;/code&gt;, I first identified and deleted all unused themes and plugins. The files kept appearing. So I started searching for terms within the files.&lt;/p&gt;

&lt;p&gt;What found me the culprit was that within &lt;code&gt;wp-temp&lt;/code&gt; the code was setting a password variable. My only thought is that this was a hash of the admin password (so I made a note to change the admin password after cleaning up). I ran the same &lt;code&gt;grep&lt;/code&gt; command but searched instead for that password value.&lt;/p&gt;

&lt;p&gt;Lo and behold, the &lt;code&gt;functions.php&lt;/code&gt; of the parent theme lit up. There, I found a bunch of hacky code that was wrapping all the pages with the malicious script and systematically ignoring the IPs where an admin user had been logged in.&lt;/p&gt;

&lt;p&gt;After deleting the code, I deleted the malicious files from within &lt;code&gt;wp-includes&lt;/code&gt; and the files didn't return. I wasn't yet certain the Malware was completely gone because I noticed that the code in &lt;code&gt;wp-temp&lt;/code&gt; was creating a cookie on the client.&lt;/p&gt;

&lt;p&gt;After deleting the code, I deleted the malicious files from within &lt;code&gt;wp-includes&lt;/code&gt; and the files didn't return. I wasn't yet certain the Malware was completely gone because I noticed that the code in &lt;code&gt;wp-temp&lt;/code&gt; was creating a cookie on the client.&lt;/p&gt;

&lt;p&gt;Before changing the admin password, I reset the &lt;code&gt;SALT Keys&lt;/code&gt; within the &lt;code&gt;wp-config.php&lt;/code&gt; file using &lt;a href="https://api.wordpress.org/secret-key/1.1/salt/" rel="noopener noreferrer"&gt;https://api.wordpress.org/secret-key/1.1/salt/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, I cleared the cache and cookies from my browser, and logged into the &lt;code&gt;wp-admin&lt;/code&gt; of the site from an incognito window. &lt;strong&gt;Only then did I reset the admin password.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Finally, I created a new admin user for myself and encouraged the client to add 2FA for their login via the &lt;a href="https://wordpress.org/plugins/miniorange-2-factor-authentication/" rel="noopener noreferrer"&gt;Google Authenticator Plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.getastra.com/blog/911/how-to-fix-wp-vcd-backdoor-hack-in-wordpress-functions-php/" rel="noopener noreferrer"&gt;https://www.getastra.com/blog/911/how-to-fix-wp-vcd-backdoor-hack-in-wordpress-functions-php/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/46219263/php-code-in-functions-php-of-all-wordpress-websites-on-my-shared-hosting" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/46219263/php-code-in-functions-php-of-all-wordpress-websites-on-my-shared-hosting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labs.sucuri.net/wp-vcd-malware-comes-with-nulled-themes/" rel="noopener noreferrer"&gt;https://labs.sucuri.net/wp-vcd-malware-comes-with-nulled-themes/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.sucuri.net/2016/05/nulled-wordpress-themes-malvertising-black-hat-seo.html" rel="noopener noreferrer"&gt;https://blog.sucuri.net/2016/05/nulled-wordpress-themes-malvertising-black-hat-seo.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. On your server, add a new user and remove root login to make it harder for someone to hack your server.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since the client already had their site installed and running on Ubuntu 18.04 a Digital Ocean droplet, the previous developer had &lt;code&gt;ssh&lt;/code&gt; access to the droplet. I really didn't want them to be able to log back in, whether or not they had nefarious intent. I noticed there was only one root user on the system, so I created a new user for myself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo adduser myuniqueusername
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then added my new user to the &lt;code&gt;sudo&lt;/code&gt; user group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo usermod -aG sudo myuniqueusername
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then I added administrative privileges to &lt;code&gt;myuniqueusername&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo visudo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within &lt;code&gt;/etc/sudoers&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myuniqueusername ALL=(ALL:ALL) ALL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, within &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PermitRootLogin no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then from command-line, restart &lt;code&gt;ssh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo service ssh restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, do whatever is necessary to &lt;a href="https://www.digitalocean.com/docs/droplets/how-to/add-ssh-keys/create-with-openssh/" rel="noopener noreferrer"&gt;create &lt;code&gt;ssh&lt;/code&gt; keys for your new account&lt;/a&gt;, set those up, logout, and then &lt;code&gt;ssh&lt;/code&gt; back in as you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Harden file permissions on the server to prevent unwarranted changes. From within the root directory of your Wordpress installation (perhaps &lt;code&gt;/var/www/html/&lt;/code&gt;) do the following:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Find what groups your user and your server belongs to and make sure that your user has ownership of all your Wordpress files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo groups
sudo usermod -aG groupname myuniqueusername
sudo find . -exec chown myuniqueusername:groupname {} +
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change files to only be writable by owner (you), and only readable by others, Change directories to be only be created, modified, or deleted by you or Wordpress, and prevent anyone other than you or wordpress from reading or writing to &lt;code&gt;wp-config&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo find . -type f -exec chmod 664 {} +
sudo find . -type d -exec chmod 775 {} +
sudo chmod 660 wp-config.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.smashingmagazine.com/2014/05/proper-wordpress-filesystem-permissions-ownerships/" rel="noopener noreferrer"&gt;https://www.smashingmagazine.com/2014/05/proper-wordpress-filesystem-permissions-ownerships/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/support/article/hardening-wordpress/#changing-file-permissions" rel="noopener noreferrer"&gt;https://wordpress.org/support/article/hardening-wordpress/#changing-file-permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Add hardening to your &lt;code&gt;httpd.conf&lt;/code&gt; Apache configuration by disabling Server banners, hardening your &lt;code&gt;.htaccess&lt;/code&gt; file, by disabling directory listing, disabling HTTP Trace, preventing MIME sniffing, preventing clickjacking, preventing some forms of cross-site scripting, and securing your cookies within &lt;code&gt;wp-config.php&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Within &lt;code&gt;/etc/apache2/httpd.conf&lt;/code&gt; or &lt;code&gt;/etc/apache2/apache2.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ServerSignature Off
ServerTokens Prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then from command-line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo service apache2 restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within &lt;code&gt;.htaccess&lt;/code&gt;, insert between &lt;code&gt;# BEGIN Wordpress&lt;/code&gt; and &lt;code&gt;# END Wordpress&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Options -Indexes
RewriteEngine On 
RewriteCond %{REQUEST_METHOD} ^TRACE 
RewriteRule .* - [F]
Header set X-Content-Type-Options nosniff
Header always append X-Frame-Options SAMEORIGIN
Header set X-XSS-Protection "1; mode=block"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within &lt;code&gt;wp-config.php&lt;/code&gt;, add to the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@ini_set('session.cookie_httponly', true);
@ini_set('session.cookie_secure', true);
@ini_set('session.use_only_cookies', true);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is some discussion over whether or not the above method is best, you could and perhaps should also add cookie hardening to your apache server configuration, see links below.&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.sucuri.net/warnings/hardening/" rel="noopener noreferrer"&gt;https://docs.sucuri.net/warnings/hardening/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://geekflare.com/wordpress-x-frame-options-httponly-cookie/" rel="noopener noreferrer"&gt;https://geekflare.com/wordpress-x-frame-options-httponly-cookie/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.stackexchange.com/questions/175436/cookie-set-without-httponly-flag" rel="noopener noreferrer"&gt;https://wordpress.stackexchange.com/questions/175436/cookie-set-without-httponly-flag&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://geekflare.com/http-header-implementation/" rel="noopener noreferrer"&gt;https://geekflare.com/http-header-implementation/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. Add Security Plugins like &lt;code&gt;Sucuri Sanner&lt;/code&gt; and &lt;code&gt;Limit Login Attempts&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/plugins/limit-login-attempts/" rel="noopener noreferrer"&gt;https://wordpress.org/plugins/limit-login-attempts/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/plugins/sucuri-scanner/" rel="noopener noreferrer"&gt;https://wordpress.org/plugins/sucuri-scanner/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The list above is by no means exhaustive, and it's just based on things I've been learning the past couple of weeks. I am very open to correction, addition, or subtraction, from the above by anyone with more experience. While I don't plan on focusing primarily on Wordpress, I'm learning more and more &lt;code&gt;LA&lt;/code&gt; of the &lt;code&gt;LAMP&lt;/code&gt; stack through this experience, and at the end of the day, with my interest in &lt;code&gt;Gatsby&lt;/code&gt;, I probably will be involved more and more with the ecosystem. &lt;/p&gt;

&lt;p&gt;That being said, &lt;strong&gt;&lt;em&gt;there is nothing wrong with learning more about website security.&lt;/em&gt;&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Even if static sites are the future, keeping our CMS and web-servers safe should not become lost or remain assumed knowledge.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>apache2</category>
      <category>htaccess</category>
      <category>security</category>
    </item>
    <item>
      <title>Improving Touch Events upon an Infinite Scrolling Component</title>
      <dc:creator>Wesley Handy</dc:creator>
      <pubDate>Mon, 10 Jun 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/wesleylhandy/improving-touch-events-upon-an-infinite-scrolling-component-kg7</link>
      <guid>https://dev.to/wesleylhandy/improving-touch-events-upon-an-infinite-scrolling-component-kg7</guid>
      <description>&lt;p&gt;&lt;a href="/static/9262257e3e1dba7123eb7236d14248d8/de376/butterfly-finger.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c9vx-Flq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.wesleylhandy.net/static/9262257e3e1dba7123eb7236d14248d8/c35de/butterfly-finger.jpg" alt="Closeup of a butterfly sitting on the tip of the someone's index finger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my recent blog post on &lt;a href="https://www.wesleylhandy.net/blog/infinite-scroll-mobile-desktop-gatsby.html"&gt;Using React Hooks to set up Infinite Scroll&lt;/a&gt;, I created a working version of infinite scroll that works in both desktop and touch screen environments. However, I came across a problem I did not anticipate when I put it into production, &lt;strong&gt;some of the links on my page stopped working&lt;/strong&gt;. I tried debugging my CSS, to no avail, only to realize the solution involved updating my &lt;code&gt;touchend&lt;/code&gt; event handlers with a very simple fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution: Debugging CSS ??
&lt;/h2&gt;

&lt;p&gt;The structure of my components nests some links within block level elements. To acheive infinite-scrolling, I map over a list of businesses as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;li key={govId} index={idx + 1}&amp;gt;
  &amp;lt;h2&amp;gt;
    &amp;lt;Link 
      to={ `/businesses/${businessName}` } state={{ 
        prevPath: typeof window !== `undefined` ? window.location.pathname : ''
      }}
    &amp;gt;
      {businessName}
    &amp;lt;/Link&amp;gt;
  &amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;
    &amp;lt;a href={`tel:${businessPhone}`}&amp;gt;{businessPhone}&amp;lt;/a&amp;gt;
  &amp;lt;/p&amp;gt;
&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;From past experience, I immediately thought the solution would be to either adjust the &lt;code&gt;z-index&lt;/code&gt; of the nested link or to set &lt;code&gt;pointer-events&lt;/code&gt; on the parent elements. I tried both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    li, li&amp;gt;h2, p { 
        pointer-events: auto;
        z-index: 1;
    }
    li&amp;gt;h2&amp;gt;a, p&amp;gt;a {
        z-index: 3;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Neither solution solved the problem nor were a problem to begin with, at least in my implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution: Debugging &lt;code&gt;touchend&lt;/code&gt; Event Handler !!
&lt;/h2&gt;

&lt;p&gt;First, here is the initial state of my code for this component. I define &lt;code&gt;handleScroll&lt;/code&gt; to add more items to the infinite scroll. Then, &lt;code&gt;handleTouchEnd&lt;/code&gt; calls the scroll event handler to avoid double loading. Take particular note of &lt;code&gt;e.preventDefault&lt;/code&gt; (perhaps this shouldn’t be called at all? We shall find out soon enough):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const handleScroll = () =&amp;gt; {
    if ( !hasMore ) return;
    if ( window &amp;amp;&amp;amp; 
       ( window.innerHeight + 
         document.documentElement.scrollTop &amp;gt;=
         document.documentElement.offsetHeight ) 
    ) {
      loadMore() // function to add more items to the infinite scroll until no items left
    }
  }

  const handleTouchEnd = (e) =&amp;gt; {
      e.preventDefault(); handleScroll();
  }

  useEffect(() =&amp;gt; {
    window &amp;amp;&amp;amp; window.addEventListener('touchend', handleTouchEnd)
    window &amp;amp;&amp;amp; window.addEventListener('scroll', handleScroll)
    window &amp;amp;&amp;amp; window.addEventListener('resize', handleScroll)
    return () =&amp;gt; {
      window &amp;amp;&amp;amp; window.removeEventListener('touchend', handleTouchEnd)
      window &amp;amp;&amp;amp; window.removeEventListener('scroll', handleScroll)
      window &amp;amp;&amp;amp; window.removeEventListener('resize', handleScroll)
    };
  }, [businesses, hasMore])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After realizing that updated my &lt;code&gt;css&lt;/code&gt; would not resolve my issue, I wondered what in the world is causing my problem. In this particular component, I use two &lt;code&gt;React&lt;/code&gt; hooks to manage state and handle events - &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt;. I wondered,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Okay, so this is a &lt;code&gt;Gatsby&lt;/code&gt; project, and I'm importing the &lt;code&gt;Link&lt;/code&gt; component from the gatsby library. Could these hooks be interfering with the functionality of the &lt;code&gt;Link&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This wasn’t the problem. I could see in the console that &lt;code&gt;Link&lt;/code&gt; rendered a simple &lt;code&gt;a&lt;/code&gt; tag, and other &lt;code&gt;Link&lt;/code&gt; components were working on the page, such as those rendered by the &lt;code&gt;Navigation&lt;/code&gt; component. The only links not working were within my infinite scrolling list component. Moreover, the links worked perfectly in a desktop environment. So I again wondered,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why in the world does this work on desktop and not on mobile? No errors are being thrown. The &lt;code&gt;href&lt;/code&gt; attribute is valid and working if I paste it in the browser. How again does the &lt;code&gt;touchend&lt;/code&gt; event work?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This led me to investigating the order in which events are fired by touch screens.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;click&lt;/code&gt; comes after &lt;code&gt;touchend&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;According to MDN, the W3C standard calls for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent#Event_order"&gt;a &lt;em&gt;typical&lt;/em&gt; order of events fired by touch screens&lt;/a&gt;, as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;touchstart&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Zero or more &lt;code&gt;touchmove&lt;/code&gt; events, depending on movement of the finger(s)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;touchend&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mousemove&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mousedown&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;click&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember I asked you to take particular note that I was calling &lt;code&gt;e.preventDefault()&lt;/code&gt; on the &lt;code&gt;touchend&lt;/code&gt; event? Turns out that is the culprit. By cancelling the dispatching of further events on &lt;code&gt;touchend&lt;/code&gt;, the &lt;code&gt;click&lt;/code&gt; event for the link component was never being fired. As MDN tells us:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the &lt;code&gt;touchstart&lt;/code&gt;, &lt;code&gt;touchmove&lt;/code&gt; or &lt;code&gt;touchend&lt;/code&gt; event is canceled during an interaction, &lt;em&gt;no mouse or click events will be fired&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The solution then must include not calling &lt;code&gt;e.preventDefault&lt;/code&gt;, particularly when a link is the target of &lt;code&gt;touchend&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, the only change necessary involves adding a condition within my &lt;code&gt;handleTouchEnd&lt;/code&gt; function, to check for &lt;code&gt;a&lt;/code&gt; tags, or &lt;code&gt;Element.tagName == "A"&lt;/code&gt;, and only call &lt;code&gt;e.preventDefault()&lt;/code&gt; if the target is not such a tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handleTouchEnd = (e) =&amp;gt; {
  if (e.target.tagName !== "A") {
    e.preventDefault(); 
    handleScroll();
  } else {
    console.log("this makes me a click event, most likely")
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@amyjoyhumphries"&gt;Amy Humphries&lt;/a&gt; on &lt;a href="https://unsplash.com/"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>touchevents</category>
      <category>react</category>
    </item>
    <item>
      <title>Adding Infinite Scroll For Both Desktop and Mobile in Your Gatsby Project with React Hooks</title>
      <dc:creator>Wesley Handy</dc:creator>
      <pubDate>Mon, 20 May 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/wesleylhandy/adding-infinite-scroll-for-both-desktop-and-mobile-in-your-gatsby-project-kp5</link>
      <guid>https://dev.to/wesleylhandy/adding-infinite-scroll-for-both-desktop-and-mobile-in-your-gatsby-project-kp5</guid>
      <description>&lt;p&gt;&lt;a href="/static/310b5d4fba2ad789399bfaeb5397a733/c35de/infinite-scroll.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8WmX7ik8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.wesleylhandy.net/static/310b5d4fba2ad789399bfaeb5397a733/c35de/infinite-scroll.jpg" alt="List of items waiting to be updated on scroll"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently created my second production &lt;a href="https://gatsbyjs.org"&gt;Gatsby&lt;/a&gt; application that gives a &lt;a href="https://vb-business-licenses.netlify.com"&gt;simple presentation of a local government open data dataset&lt;/a&gt;. I say production, though, much of the application is a proof-of-concept for a bigger application I have in the works (perhaps a startup in the mix? Not sure yet...). My app includes over 2400 nodes, so I needed a way to present the data in user-friendly ways. Each record in my collection included a set of categories, so I could easily create a categories page and split the data that way. However, I also want to eventually add search and also allow for a user to browse through the entire dataset. This is where I looked into &lt;code&gt;pagination&lt;/code&gt; and &lt;code&gt;infinite-scroll&lt;/code&gt;. You can &lt;a href="https://www.gatsbyjs.org/docs/adding-pagination/"&gt;read about adding pagination on the Gatsby blog&lt;/a&gt;—it’s pretty straight-forward. Below is how I set up infinite-scroll that works in both development and production environments, as well as both in the browser and on touch screens. I was able to accomplish this using React &lt;code&gt;Hooks&lt;/code&gt; within a functional component rather than a class-based component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inifinite Scroll &amp;amp; React Hooks
&lt;/h2&gt;

&lt;p&gt;As much as this post is about integrating this within Gatsby, this is properly a &lt;code&gt;react&lt;/code&gt; question. Gatsby is simply a platform for sourcing data. This could easily be implemented via &lt;code&gt;fetch&lt;/code&gt;-ing of data from an API during client-side component. Adjust this method to what you need in your case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up &lt;code&gt;gatsby-node.js&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Within &lt;code&gt;gatsby-node.js&lt;/code&gt; you will define an export named &lt;code&gt;createPages&lt;/code&gt; that queries &lt;code&gt;graphql&lt;/code&gt; for nodes from your data source and returns that list of nodes. Before returning, you can call the &lt;code&gt;createPage&lt;/code&gt; API as many times a you need to generate your site. I intend, among many other things, to create a single page that generates and infinite scroll through my list of businesses. I will call &lt;code&gt;createPage&lt;/code&gt;, passing to it the path of the page, the template for the page, and data that will be provided to the client via the &lt;code&gt;Context&lt;/code&gt; api:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports.createPages = ({ actions, graphql }) =&amp;gt; {
  const { createPage } = actions
  return graphql(`
    {
      #some query specific to your source data
      specificNameOfYourQuery {
        edges {
          node {
            #specific fields
          }
        }
      }
    }
  `).then(result =&amp;gt; {
    if (result.errors) {
      return Promise.reject(result.errors)
    }
    const { data: [specificNameOfYourQuery]: edges } } } = result;
    const infiniteScrollTemplate = path.resolve(`src/templates/infinite-scroll-template.js`)
    createPage({ 
      path: "/businesses", 
      component: infiniteScrollTemplate, 
      context: { 
        edges,
      },
    }) return edges;
  })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating the Template that Will Include Infinite Scroll
&lt;/h3&gt;

&lt;p&gt;From the root of your project, go into your &lt;code&gt;src&lt;/code&gt; directory, create a &lt;code&gt;templates&lt;/code&gt; folder if it doesn’t already exist, then create your template page. I entitled mine &lt;code&gt;infinite-scroll-template.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd src
mkdir templates
cd templates
touch infinite-scroll-template.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  React Hooks - &lt;code&gt;useEffect&lt;/code&gt; and &lt;code&gt;useState&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Once you have opened your template file within your IDE, you will need to import &lt;code&gt;React&lt;/code&gt; as well as the &lt;code&gt;useEffect&lt;/code&gt; and &lt;code&gt;useState&lt;/code&gt; hooks. For Gatsby projects, you will also import your &lt;code&gt;Layout&lt;/code&gt; component so that your page will match the rest of your site. The &lt;code&gt;createPage&lt;/code&gt; API passes to your component &lt;code&gt;pageContext&lt;/code&gt; as props where you can access the list of &lt;code&gt;edges&lt;/code&gt; you pass to your template from &lt;code&gt;gatsby-node&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect } from 'react'
import Layout from '../components/Layout'

function InfiniteScroll({ pageContext: { edges } }) {
    return null
}

function InfiniteScrollTemplate(props) {
  return (
    &amp;lt;Layout {...props}&amp;gt;
      &amp;lt;InfiniteScroll {...props}/&amp;gt;
    &amp;lt;/Layout&amp;gt;
  )
}

export default InfiniteScrollTemplate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We will focus on adding the core logic for infinite scroll to the &lt;code&gt;InfiniteScroll&lt;/code&gt; functional component. We do not need to declare a &lt;code&gt;React&lt;/code&gt; class because of the two aforementioned hooks—:&lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Creating and Setting Internal State with &lt;code&gt;useState&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;React hooks allow us to write functional components that can “hook into” other React features. &lt;code&gt;useState&lt;/code&gt; allows us to add state that is preserved between renders of a functional component. Unlike &lt;code&gt;state&lt;/code&gt; within a React &lt;code&gt;class&lt;/code&gt;, &lt;code&gt;useState&lt;/code&gt; replaces the previous state rather than merging with previous state. Calling &lt;code&gt;useState&lt;/code&gt; takes only one argument, whatever you conceive of as the initial state. The &lt;code&gt;useState&lt;/code&gt; hook can be called multiple times, so rather than having a single &lt;code&gt;state&lt;/code&gt; object, you can have multiple &lt;code&gt;state&lt;/code&gt;-like variables. This is because the call to &lt;code&gt;useState&lt;/code&gt; returns an array of two properties - the value of the current state and a function to call to update the value of that state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [currentState, setState] = useState(/* some initial value or function that returns a value */)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For infinite scroll to work in this example, we need two state variables—a &lt;code&gt;boolean&lt;/code&gt; indicating if there are more records to load and an &lt;code&gt;array&lt;/code&gt; of the records already loaded. Seed the &lt;code&gt;currentList&lt;/code&gt; with the first 10 records. Don’t worry if there is the possibility that the initial set is less than 10, &lt;code&gt;Array.slice&lt;/code&gt; will return all records up to the length of the array if you provide an ending value greater than the last index of the array.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: if we had a situation where you loaded data asynchronously from an API, we would also need someway to determine if data was in loading state&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [hasMore, setMore] = useState(edges.length &amp;gt; 10)
const [currentList, addToList] = useState([...edges.slice(0, 10)])
const [isLoading, setLoading] = useState(false) // if loading from an API asynchronously
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h5&gt;
  
  
  Creating Event Handlers to Read and Set State
&lt;/h5&gt;

&lt;p&gt;Reading and setting state will occur within an event listener on the scroll position of the page. If you use an external api and that api is still loading content, we will return immediately, and we will also exit if we know there are no more edges to load. Otherwise, we will check to see if the scroll position of the &lt;code&gt;document&lt;/code&gt; plus the &lt;code&gt;innerHeight&lt;/code&gt; of the window equals the &lt;code&gt;offsetHeight&lt;/code&gt; of the document, and if so, we can load more edges. Basically, this checks to see if the page is scrolled all the way to the bottom.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handleScroll = () =&amp;gt; {
  if ( !hasMore || isLoading ) return;
  if ( window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight ){
    loadEdges(true)
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;loadEdges&lt;/code&gt; function will do the following, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;if using an asynchronously api call, will set the loading flag to true&lt;/li&gt;
&lt;li&gt;determine if any more edges remaining&lt;/li&gt;
&lt;li&gt;slice a new chunk of edges and append to the current list&lt;/li&gt;
&lt;li&gt;if using an asynchronously api call, will return the loading flag to false&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since I’m loading from the &lt;code&gt;Context&lt;/code&gt; API, I will ignore steps 1 &amp;amp; 2 above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const loadEdges = () =&amp;gt; {
  const currentLength = currentList.length
  const more = currentLength &amp;lt; edges.length
  const nextEdges = more ? edges.slice(currentLength, currentLength + 20) : []
  setMore(more)
  addToList([...currentList, ...nextEdges])
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;How would &lt;code&gt;isLoading&lt;/code&gt; be used?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is one overly simplistic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const loadEdges = async () =&amp;gt; {
  setLoading(true);
  try {
      const newEdges = await fetch('https://path/to/some/api')
      const more = newEdges.length &amp;gt; 0
      setMore(more)
      addBusinesses([...currentList, ...nextEdges])
  } catch(err) {
      console.error({fetchNewEdgesError: err})
  }
  setLoading(false)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h5&gt;
  
  
  Checking The Scroll Position on Each Render with &lt;code&gt;useEffect&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;The final step to initializing infinite scroll is the &lt;code&gt;useEffect&lt;/code&gt; hook. The hook &lt;code&gt;useEffect&lt;/code&gt; takes two arguments: a function, and an array. The first argument is the function that will be called every time &lt;em&gt;after&lt;/em&gt; the component is rendered. This function is allowed to return another function which will be remembered and gets called as a cleanup function (I’m not sure I fully understand cleanups yet, but I think &lt;a href="https://overreacted.io/a-complete-guide-to-useeffect/"&gt;Dan Abramov does&lt;/a&gt;). The second argument is an array of dependencies that would prevent an effect from being called if the values of those dependencies are unchanged between renders. Infinite scroll will a function with a cleanup callback as well as the array full of dependencies to work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(
  () =&amp;gt; {
    /* function that gets called every time */
    return () =&amp;gt; {
      /* cleanup function to be called */
    }
  }, [/* dependencies */])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useEffect&lt;/code&gt; is a great place to initialize event listeners on the &lt;code&gt;window&lt;/code&gt; or &lt;code&gt;document&lt;/code&gt;, as well as to remove those listeners during cleanup, such as listening for &lt;code&gt;scroll&lt;/code&gt; events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  window.addEventListener('scroll', handleScroll)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And thus, the cleanup function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  window.removeEventListener('scroll', handleScroll)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This gives us an almost complete &lt;code&gt;useEffect&lt;/code&gt; function call, we will simply add our state variables to the dependencies array so that effect is only set or cleaned up when the variables change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(
  () =&amp;gt; {
    window.addEventListener('scroll', handleScroll)
    return () =&amp;gt; {
      window.removeEventListener('scroll', handleScroll)
    }
  }, [hasMore, isLoading, currentList])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With state, event handlers, effects initialized, we are free to return the &lt;code&gt;jsx&lt;/code&gt; for the scrolling list. The following maps over the &lt;code&gt;currentList&lt;/code&gt; array. It also adds labels displaying the current state of the list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (
  &amp;lt;&amp;gt; {/* shorthand for React.Fragment */}
    &amp;lt;ul&amp;gt;
      {
        currentList.map(({node: { fields }}, idx) =&amp;gt; {
          return (
            &amp;lt;li key={`fields-${idx}`} index={idx + 1}&amp;gt;
              { 
                /* you will know the specifics here from how you load your data */
                fields 
              }
            &amp;lt;/li&amp;gt;
          )
        })
      }
    &amp;lt;/ul&amp;gt;
    {
      !hasMore &amp;amp;&amp;amp;
        &amp;lt;div&amp;gt;All Businesses Loaded!&amp;lt;/div&amp;gt;
    }
    {
      hasMore &amp;amp;&amp;amp;
        &amp;lt;div&amp;gt;Scroll Down to Load More...&amp;lt;/div&amp;gt;
    }
    {
      /* if using this flag, otherwise omit */
      isLoading &amp;amp;&amp;amp; 
        &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;
    }
  &amp;lt;/&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Putting It All Together
&lt;/h3&gt;

&lt;p&gt;Now we have a complete picture of the infinite scroll functional component. See below, but don’t leave yet, we still have to account for &lt;code&gt;gatsby build&lt;/code&gt; and mobile events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect } from 'react'
import Layout from '../components/Layout'

function InfiniteScroll({ pageContext: { edges } }) {
  const [hasMore, setMore] = useState(edges.length &amp;gt; 10)
  const [currentList, addToList] = useState([...edges.slice(0, 10)])

  const loadEdges = () =&amp;gt; {
    const currentLength = currentList.length
    const more = currentLength &amp;lt; edges.length
    const nextEdges = more ? edges.slice(currentLength, currentLength + 20) : []
    setMore(more)
    addToList([...currentList, ...nextEdges])
  }

  const handleScroll = () =&amp;gt; {
    if ( !hasMore ) return;
    if ( window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight ){
      loadEdges()
    }
  }

  useEffect(() =&amp;gt; {
    window.addEventListener('scroll', handleScroll)
    return () =&amp;gt; {
      window.removeEventListener('scroll', handleScroll)
    }
  }, [hasMore, currentList])

  return (
    &amp;lt;&amp;gt; {/* shorthand for React.Fragment */}
      &amp;lt;ul&amp;gt;
        {
          currentList.map(({node: { fields }}, idx) =&amp;gt; {
            return (
              &amp;lt;li key={`fields-${idx}`} index={idx + 1}&amp;gt;
                { 
                  /* you will know the specifics here from how you load your data */
                  fields 
                }
              &amp;lt;/li&amp;gt;
           )
          })
        }
      &amp;lt;/ul&amp;gt;
      {
        !hasMore &amp;amp;&amp;amp;
          &amp;lt;div&amp;gt;All Businesses Loaded!&amp;lt;/div&amp;gt;
      }
      {
        hasMore &amp;amp;&amp;amp;
          &amp;lt;div&amp;gt;Scroll Down to Load More...&amp;lt;/div&amp;gt;
      }
      {
    &amp;lt;/&amp;gt;
  )
}

function InfiniteScrollTemplate(props) {
  return (
    &amp;lt;Layout {...props}&amp;gt;
      &amp;lt;InfiniteScroll {...props}/&amp;gt;
    &amp;lt;/Layout&amp;gt;
  )
}

export default InfiniteScrollTemplate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This functional component will work just fine during development on a desktop browser. But you will lose the scroll effect during the build and on touch screens. It will fail during build because client globals like &lt;code&gt;window&lt;/code&gt; and &lt;code&gt;document&lt;/code&gt; are undefined during the build. It will fail on mobile because &lt;code&gt;scroll&lt;/code&gt; is a mouse event. We need to add some conditions for our event listeners to handle both conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimizing for Production and Touch Screens
&lt;/h3&gt;

&lt;p&gt;In addition to adding an event listener on &lt;code&gt;scroll&lt;/code&gt;, we also need event listeners for &lt;code&gt;touchend&lt;/code&gt; and &lt;code&gt;resize&lt;/code&gt; (to handle situations where someone resizes their browser), plus we need to add &lt;code&gt;preventDefault&lt;/code&gt; to touch events to prevent duplicate events being fired in certain situations where devices have both a mouse and a touch screen. First, create a new event handler for handling &lt;code&gt;touchend&lt;/code&gt;. This new handler will simply prevent the default actions on &lt;code&gt;touchend&lt;/code&gt; and call the handler for the scroll event (which simply checks to see if we should load more documents). Second, update the &lt;code&gt;useEffect&lt;/code&gt; function to add the additional event handlers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handleTouchEnd = (e) =&amp;gt; { e.preventDefault(); handleScroll();}
useEffect(() =&amp;gt; {
  window.addEventListener('scroll', handleScroll)
  window.addEventListener('resize', handleScroll)
  window.addEventListener('touchend', handleTouchEnd)
  return () =&amp;gt; {
    window.removeEventListener('scroll', handleScroll)
    window.removeEventListener('resize', handleScroll)
    window.removeEventListener('touchend', handleTouchEnd)
  }
}, [hasMore, currentList])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Finally, we need to add a few simple &lt;code&gt;boolean&lt;/code&gt; checks (&lt;code&gt;window &amp;amp;&amp;amp;&lt;/code&gt;) to every instance of &lt;code&gt;window&lt;/code&gt; or &lt;code&gt;document&lt;/code&gt; so that the build process succeeds &lt;em&gt;and&lt;/em&gt; so that infinite scroll still operates in the client. Plus, we need to change the scroll position check to be &lt;code&gt;&amp;gt;=&lt;/code&gt; instead of the strict equality &lt;code&gt;===&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handleScroll = () =&amp;gt; {
  if ( !hasMore || isLoading ) return;
  if ( window &amp;amp;&amp;amp; ( ( window.innerHeight + document.documentElement.scrollTop ) &amp;gt;= document.documentElement.offsetHeight ) ){ loadEdges()
  }
}
useEffect(() =&amp;gt; {
  window &amp;amp;&amp;amp; window.addEventListener('scroll', handleScroll)
  window &amp;amp;&amp;amp; window.addEventListener('resize', handleScroll)
  window &amp;amp;&amp;amp; window.addEventListener('touchend', handleTouchEnd)
  return () =&amp;gt; {
    window &amp;amp;&amp;amp; window.removeEventListener('scroll', handleScroll)
    window &amp;amp;&amp;amp; window.removeEventListener('resize', handleScroll)
    window &amp;amp;&amp;amp; window.removeEventListener('touchend', handleTouchEnd) }
}, [hasMore, currentList])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There you have it! You have a component that will implement infinite scroll in the browser, on touch screens, and in production within a Gatsby or React application.&lt;/p&gt;

&lt;p&gt;Check out the following page in your browser and on mobile to &lt;a href="https://vb-business-licenses.netlify.com/businesses"&gt;see this code in action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also see &lt;a href="https://github.com/wesleylhandy/got-business-client"&gt;my specific implementation of the source code on github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>gatsby</category>
      <category>infinitescroll</category>
      <category>reacthooks</category>
    </item>
    <item>
      <title>Putting SEO First with Gatsby</title>
      <dc:creator>Wesley Handy</dc:creator>
      <pubDate>Sat, 20 Apr 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/wesleylhandy/putting-seo-first-with-gatsby-3n2g</link>
      <guid>https://dev.to/wesleylhandy/putting-seo-first-with-gatsby-3n2g</guid>
      <description>&lt;p&gt;&lt;a href="/static/d580489e8877a5a2439959d5d0b2b3cd/de376/gatsby-stickers.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5kRUK258--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.wesleylhandy.net/static/d580489e8877a5a2439959d5d0b2b3cd/c35de/gatsby-stickers.jpg" alt="Gatsby loves to claim to produce the fastest sites around"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was drawn to &lt;a href="https://www.gatsbyjs.org/"&gt;Gatsby&lt;/a&gt; out of my love for &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;, and my desire to serve fast, perfomant, responsive applications. I started learning React just as the library started embracing ES6 classes, and just as &lt;code&gt;create-react-app&lt;/code&gt; was taking off. It was a challenge at first to learn the simplicity of proxying to get the front end to run concurrently with a &lt;code&gt;node&lt;/code&gt; / &lt;code&gt;express&lt;/code&gt; API. And yet, to get from that point to server-side rendering took a little more research and effort. It has been fun learning with and from the larger coding community. I could continue to enumerate the other technical issues I have encountered and solved, and yet some others I still have to learn, but note something important so far regarding my journey—it has been a &lt;em&gt;techinical-first-journey&lt;/em&gt;, focused on knowledge and skill of the various langauges and libraries, necessary yes, but perhaps not primary. The last thing on mind has been &lt;strong&gt;SEO&lt;/strong&gt; , &lt;strong&gt;accessbility&lt;/strong&gt; , and &lt;strong&gt;security&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting An SEO-First Mindset
&lt;/h2&gt;

&lt;p&gt;I’ll leave accessibility and then security future posts, but what has impressed me thus far in my dive into the &lt;code&gt;Gatbsy&lt;/code&gt; ecosystem has been &lt;strong&gt;the ease to configure search engine optimization&lt;/strong&gt;. In fact, you can create an architecture for your site or app that is SEO driven long before developing your UI. I want to walk you through the things I have learned so far in optimizing a Gatsby site for SEO right from the start.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Before We Begin&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;You need to have some familiarity with Gatsby. To learn how to install the &lt;code&gt;gatbsy-cli&lt;/code&gt; and create a new Gatsby project from one of the many Gatsby Starters, please consider &lt;a href="https://www.gatsbyjs.org/tutorial/"&gt;completing the Gatsby tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Otherwise, &lt;a href="https://www.gatsbyjs.org/starters/?c=SEO&amp;amp;v=2"&gt;pick a starter&lt;/a&gt; from the SEO category, and open the SEO component or just use the &lt;code&gt;gatsby-starter-default&lt;/code&gt; by running from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gatsby new seo-blog https://github.com/gatsbyjs/gatsby-starter-default
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The SEO component is dependent upon &lt;code&gt;react-helmet&lt;/code&gt;, if your starter does not come with SEO initialized be sure to add it. We also want to add some other features like sitemap, google analytics, and RSS feed, for syndication. Finally, we need to create &lt;code&gt;robots.txt&lt;/code&gt; to manage how search engines crawl the site. I’ve split up the commands below for spacing purposes, but they can all run as one &lt;code&gt;yarn add&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add react-helmet gatsby-plugin-react-helmet 
yarn add gatsby-plugin-feed gatsby-plugin-google-analytics gatsby-plugin-sitemap
yarn add gatsby-plugin-robots-txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With your starter and these plugins installed, we are ready to set up our site for SEO Performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up &lt;code&gt;gatsby-config.js&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Within the root of your new Gatsby project sits the file Gatsby uses to configure &lt;code&gt;siteMetaData&lt;/code&gt; and &lt;code&gt;plugins&lt;/code&gt; and &lt;a href="https://www.gatsbyjs.org/docs/gatsby-config/"&gt;several other important features&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This file, &lt;code&gt;gatsby-config.js&lt;/code&gt; is going to do the heavy lifting of importing all your vital SEO related content into GraphQL or create necessary files directly (like &lt;code&gt;robots.txt&lt;/code&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Site Metadata
&lt;/h4&gt;

&lt;p&gt;Metadata is as it sounds, data that extends across or throughout the entirity of your site. It can be used anywhere, but will come most in handy when configuring your SEO component as well as Google Structured Data.&lt;/p&gt;

&lt;p&gt;Initialize your metadata as an Object literal with key/value pairs that can be converted into &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags, or can be passed to sitemaps or footers, wherever you might need global site data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta name="title" content="My Super Awesome Site"/&amp;gt;
&amp;lt;meta name="description" content="My Super Awesome Site will blow your mind with radical content, extravagant colors, and hip designs."/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here is where you need to plan what might use across your site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; title&lt;/li&gt;
&lt;li&gt; description&lt;/li&gt;
&lt;li&gt; keywords&lt;/li&gt;
&lt;li&gt; site verification&lt;/li&gt;
&lt;li&gt; social links&lt;/li&gt;
&lt;li&gt; other&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;code&gt;siteMetadata&lt;/code&gt; set up, your can now query this data to be used within your plugin initialization, even within the same &lt;code&gt;gatsby-config.js&lt;/code&gt; file. I’ve organized my &lt;code&gt;siteMetadata&lt;/code&gt; so that I can make the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query: `
  site {
    siteMetadata {
      title
      description
      author
      siteUrl
      siteVerification {
        google
        bing
      }
      social {
        twitter
      }
      socialLinks {
        twitter
        linkedin
        facebook
        stackOverflow
        github
        instagram
        pinterest
        youtube
        email
        phone
        fax
        address
      }
      keywords
      organization {
        name
        url
      }
    }
  }
`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This query returns an object matching this query structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;site: {
  siteMetadata: {
    title: String,
    description: String,
    author: String,
    siteUrl: String,
    siteVerification: {
      google: String,
      bing: String
    },
    social: {
      twitter: String
    },
    socialLinks: {
      twitter: String,
      linkedin: String,
      facebook: String,
      stackOverflow: String,
      github: String,
      instagram: String,
      pinterest: String,
      youtube: String,
      email: String,
      phone: String,
      fax: String,
      address: String
    },
    keywords: [String],
    organization: {
      name: String,
      url: String
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Plugin Setup
&lt;/h4&gt;

&lt;p&gt;We are going to focus on four plugins, each for the sitemap, RSS feed, robots.txt file, and Google Analytics (for tracking the SEO success of your site). We’ll initialize the plugins immediately following &lt;code&gt;siteMetaData&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = {
    siteMetadata: {
      / **SEE ABOVE** /
    },
    plugins: [/ **An ARRAY** /], }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;gatsby-plugin-sitemap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The sitemap plugin can be used simply or with options. Generally, you want to include anything and everything with high quality content, and &lt;strong&gt;&lt;em&gt;exclude duplicate content, thin content, or pages behind authentication&lt;/em&gt;&lt;/strong&gt;. Gatsby provides a &lt;a href="https://www.gatsbyjs.org/docs/creating-a-sitemap/"&gt;detailed walkthrough for setting up your sitemap&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins: [
{
resolve: `gatsby-plugin-sitemap`,
options: {
  exclude: [`/admin`, `/tags/links`] 
}
},
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;gatsby-plugin-feed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;An RSS feed helps with syndication of content on your site, like if you had a blog and wanted to cross-post to another better established blog, and it helps communicate changes to your site to search engines.&lt;/em&gt;&lt;/strong&gt; This plugin allows you to create any number of different feeds utlizing the power of GraphQL to query your data. Some of what is below is dependent on how you structure your pages and posts in &lt;code&gt;gatsby-node.js&lt;/code&gt;. The feed will walk through each of your pages in &lt;code&gt;markdown&lt;/code&gt; generate an XML RSS Feed. Gatsby provides a &lt;a href="https://www.gatsbyjs.org/docs/adding-an-rss-feed/"&gt;detailed walkthrough for adding an RSS feed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note particularly the use of queries below to complete the feed.&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
resolve: `gatsby-plugin-feed`,
options: {
// graphQL query to get siteMetadata 
query: ` 
  { 
    site { 
      siteMetadata { 
        title 
        description 
        siteUrl 
        site_url: siteUrl, 
        author 
      }
    }
  } 
`, 
feeds: [
  // an array of feeds, I just have one below
  {
    serialize: ({ query: { site, allMarkdownRemark } }) =&amp;gt; {
      const { siteMetadata : { siteUrl } } = site;
      return allMarkdownRemark.edges.map(edge =&amp;gt; {
        const { 
          node: { 
            frontmatter: {
              title, 
              date, 
              path, 
              author: { name, email }, 
              featured: { publicURL }, 
              featuredAlt
            },
            excerpt, 
            html
          } 
        } = edge;
        return Object.assign({}, edge.node.frontmatter, {
          language: `en-us`,
          title,
          description: excerpt,
          date,
          url: siteUrl + path,
          guid: siteUrl + path,
          author: `${email} ( ${name} )`,
          image: {
            url: siteUrl + publicURL,
            title: featuredAlt,
            link: siteUrl + path,
          },
          custom_elements: [{ "content:encoded": html }],
        })
      })
    },
    // query to get blog post data 
    query: ` 
      { 
        allMarkdownRemark(  
          sort: { order: DESC, fields: [frontmatter___date] }, 
        ) { 
          edges { 
            node { 
              excerpt 
              html 
              frontmatter { 
                path 
                date 
                title 
                featured { publicURL } 
                featuredAlt 
                author { 
                  name 
                  email 
                } 
              } 
            } 
          } 
        } 
      } 
    `, 
    output: "/rss.xml",
    title: `My Awesome Site | RSS Feed`,
  },
],
},
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;gatsby-plugin-robots-txt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;robots.txt&lt;/code&gt; files, you can instruct crawlers to ignore your site and/or individual paths, based on certain conditions. &lt;a href="https://www.gatsbyjs.org/packages/gatsby-plugin-robots-txt/?=robots"&gt;See the plugin description for more detailed use cases&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
resolve: 'gatsby-plugin-robots-txt',
options: {
policy: [{ userAgent: '*', allow: '/' }] 
}
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;gatsby-plugin-google-analytics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google analytics will help you track how users find and interact with your site, so you can manage and update your site over time for better SEO results. Verify your site with Google and store your analytics key here:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
resolve: `gatsby-plugin-google-analytics`,
options: {
trackingId: ``, 
},
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Optimizing the SEO Component
&lt;/h3&gt;

&lt;p&gt;The secret sauce behind the SEO Component is the well-known &lt;code&gt;react-hemlet&lt;/code&gt; package. Every page and every template imports the SEO Component and thus you can pass specific information to adjust the metadata for each public facing page.&lt;/p&gt;

&lt;p&gt;Use your imagination—what can you pass to this component to create the perfect SEO enabled page? Is the page a landing page, a blog post, a media gallery, a video, a professional profile, a product? There are 614 types of schemas listed on &lt;a href="https://schema.org/docs/schemas.html"&gt;https://schema.org&lt;/a&gt;. You can pass specific schema related information to the SEO component.&lt;/p&gt;

&lt;p&gt;If any of these values you pass to the component, a &lt;code&gt;StaticQuery&lt;/code&gt; would fill in what’s missing with &lt;code&gt;siteMetaData&lt;/code&gt;. From this data, &lt;code&gt;react-helmet&lt;/code&gt; creates all the &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags, including &lt;code&gt;open graph&lt;/code&gt; and &lt;code&gt;twitter&lt;/code&gt; card tags, and pass relevant data to another component that returns &lt;code&gt;Google Structured Data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rather than including a long code-snippet, refer back to the Before We Begin section, or &lt;a href="https://www.gatsbyjs.org/packages/gatsby-plugin-react-helmet/"&gt;refer to the &lt;code&gt;gatsby-plugin-react-helment&lt;/code&gt; page for installation&lt;/a&gt;. But please note, the structure of my SEO Component follows that outlined by &lt;a href="https://blog.dustinschau.com/search-engine-optimization-with-gatsby"&gt;this excellent post&lt;/a&gt; by Dustin Schau.&lt;/p&gt;

&lt;p&gt;As I have tinkered with the SEO Component, here are the features I added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Additional Fields Passed to Component to: distinguish between types of pages, such as blog posts, to handle authors of content other than main site author, and to handle changes in date modified for pages and posts. More could be added in the future.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function SEO({
  description,
  lang,
  meta,
  keywords,
  image,
  title,
  pathname,
  isBlogPost,
  author,
  datePublished = false,
  dateModified = false
}) {
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Setting &lt;code&gt;og:type&lt;/code&gt; conditionally
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  property: `og:type`,
  content: isBlogPost ? `article` : `website`,
},
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Always setting an &lt;code&gt;alt&lt;/code&gt; property on the image object
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ALWAYS ADD IMAGE:ALT 
{ property: "og:image:alt", content: image.alt }, 
{ property: "twitter:image:alt", content: image.alt },
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Handling Secure Images
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.concat(
  // handle Secure Image 
  metaImage &amp;amp;&amp;amp; metaImage.indexOf("https") &amp;gt; -1 
    ? [
        { property: "twitter:image:secure_url", content: metaImage, }, 
        { property: "og:image:secure_url", content: metaImage },
    ] 
    : [] 
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Adding a Component to handle &lt;code&gt;Google Structured Data&lt;/code&gt; using &lt;a href="https://schema.org"&gt;schema.org&lt;/a&gt; categories. I came across an example of such a component from reading through various documentation and articles, I can’t recall where I saw the link first, but I borrowed and adapted &lt;a href="https://github.com/jlengstorf/gatsby-theme-jason-blog/blob/e6d25ca927afdc75c759e611d4ba6ba086452bb8/src/components/SEO/SchemaOrg.js"&gt;from Jason Lengstorf&lt;/a&gt;. I made two small adaptations to his excellent work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configuring the SchemaOrg Component
&lt;/h3&gt;

&lt;p&gt;You will import and call the &lt;code&gt;SchemaOrg&lt;/code&gt; Component from within the &lt;code&gt;SEO&lt;/code&gt; Component and place it just after the closing tag of the &lt;code&gt;Helmet&lt;/code&gt; Component and pass the following properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function SEO(.....) {
  ...
  return (
    &amp;lt;&amp;gt; {/* Fragment Shorthand */}
      &amp;lt;Helmet 
        {/* All the Meta Tag Configuration */}
      /&amp;gt;
      &amp;lt;SchemaOrg 
        isBlogPost={isBlogPost} 
        url={metaUrl} 
        title={title} 
        image={metaImage} 
        description={metaDescription} 
        datePublished={datePublished} 
        dateModified={dateModified} 
        canonicalUrl={siteUrl} 
        author={isBlogPost ? author : siteMetadata.author} 
        organization={organization} 
        defaultTitle={title} 
      /&amp;gt;   
    &amp;lt;/&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I won’t copy and paste the entire &lt;code&gt;SchemaOrg&lt;/code&gt; Component here. Grab it from the link above, and give Jason Lengstorf some credit in your code. Below are the few additions I made:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I added author email to the Schema. This will come from &lt;code&gt;siteMetadata&lt;/code&gt; for most pages and from post &lt;code&gt;frontmatter&lt;/code&gt; from blog posts. This will support multiple authors for your site and each page can reflect that uniquely if you so choose.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;author: {
  "@type": "Person",
  name: author.name,
  email: author.email, 
},
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;I updated the organization logo from a simple URI to an &lt;code&gt;ImageObject&lt;/code&gt; type. While the &lt;code&gt;String&lt;/code&gt; URI is acceptable to the &lt;code&gt;Organization&lt;/code&gt; type, Google has specific expectations and was throwing an error until I changed it to an &lt;code&gt;ImageObject&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;publisher: {
  "@type": "Organization",
  url: organization.url,
  logo: { 
    "@type": "ImageObject", 
    url: organization.logo.url, 
    width: organization.logo.width, 
    height: organization.logo.height
  }, 
  name: organization.name,
},
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;dataModified&lt;/code&gt; to reflect changes to the page after initial publication for the &lt;code&gt;BlogPosting&lt;/code&gt; type.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;datePublished,
dateModified,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When you have more complexity within you site, you can pass flags to this Component to return differing types of schema as needed. But no matter what you do, do not put your site into production without first passing through the &lt;code&gt;script&lt;/code&gt; generated by the component to the &lt;a href="https://search.google.com/structured-data/testing-tool/u/0/"&gt;Google Structured Data Testing Tool&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concluding Thoughts
&lt;/h2&gt;

&lt;p&gt;When I configured my site according to the plan I outlined above, not only do I get image rich, descriptive Social Sharing cards, I get perfect SEO scores when running a Lighthouse Audit on my site:&lt;/p&gt;

&lt;p&gt;&lt;a href="/static/384cdb474a81538eeda376637170d853/ac127/lighthouse_audit_seo.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q5WIQRNj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.wesleylhandy.net/static/384cdb474a81538eeda376637170d853/ac127/lighthouse_audit_seo.png" alt="Lighthouse Audit gives my website a 100% score for Search Engine Optimization"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You also see that I scored 100% for Accessibility on my site. This is so easy to score with Gatsby as well, and I’ll talk about what I learned on this topic in the future.&lt;/p&gt;

&lt;p&gt;Originally Posted on my blog at &lt;a href="https://www.wesleylhandy.net/blog/seo-accessibility-first-gatsby.html"&gt;https://www.wesleylhandy.net/blog/seo-accessibility-first-gatsby.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>seo</category>
      <category>sitemap</category>
      <category>structureddata</category>
    </item>
  </channel>
</rss>
