{
  "$type": "site.standard.document",
  "content": {
    "$type": "site.standard.content.markdown",
    "text": "![Home page screenshot](https://eligundry.com/../../assets/img/website-update/home-page.png)\n\nIn the age of social media and hosted blogs, personal websites don't get the love they deserve. Sure, writing developer\nbased content on a site like [medium.com](https://medium.com/) or [dev.to](https://dev.to/) will get more eyeballs on it than something you\nmake on your own, but there is something extremely satisfying in owning every pixel on a site with your name on it.\nI saw this Tweet the other day and it seemed fitting to how I think about personal sites.\n\n<Tweet id=\"1456360721084674098\">\n  <blockquote>\n    <p lang=\"en\" dir=\"ltr\">\n      trying this analogy thing\n      <a href=\"https://t.co/Q9889HOF3B\">pic.twitter.com/Q9889HOF3B</a>\n    </p>\n    &mdash; Jane Manchun Wong (@wongmjane)\n    <a href=\"https://twitter.com/wongmjane/status/1456360721084674098?ref_src=twsrc%5Etfw\">\n      November 4, 2021\n    </a>\n  </blockquote>\n</Tweet>\n\nIf we are entering the era of Web3 (which is Soylent in this Tweet, ewww) and it's ethos is trying to revive the\ntechno-optimisim and ownership of the Web 1.0 era, what better way to embody this than with an over-engineered personal\nsite? Also, I just really love Greek salad (I live in Astoria after all, you walk outside on Ditmars and they force\nGreek salad on you).\n\n> As an aside, I find all of Web3 and blockchain stuff disgusting. I have no money in any of that stuff because I object\n> on environmental issues and that most of it reeks of pump and dump schemes. That said, I am happy for anything that\n> pushes the web pendulum back in the direction of user's owning pixels attached to their name.\n\n# Guiding Principals\n\n1. Dynamic enough to reflect me\n2. Fetch data at compile time\n3. Do a lot and do it fast\n4. Updates should be triggered by tools/apps I use instead of manual intervention\n\n# Technology\n\nWould be a technology blog if I didn't nerd out about tools before describing the cool things I did?\n\n## Gatsby\n\nI moved this site from [Lektor][lektor] to [Gatsby][gatsby] last year while procrastinating on writing a talk about\n[Redux][redux-talk]. At my last job, I was working almost exclusively in Python + Flask, so using a Python based\ngenerator made sense at the time. But now, at my current job, I'm working a lot more in JavaScript and I wanted my site\nto be more dynamic. I also knew that we were planning on migrating our customer facing sites to a static site generator\nin the near future and Gatsby was high up on the list of possibilities for us to use. By picking it up for my personal\nsite, I could learn how to use it beforehand and know exactly what we needed to do.\n\nTurns out that this was a fantastic bet on the future. We are porting around 10 sites at work to Gatsby and because\nI had learned the basics of Gatsby months previous on a single site, I was able to dive into the more complex topics to\nenable us to port many sites at once.\n\nWhile doing some of these more complex things in a private repo at work, I have been able to take aspects that I have\nlearned there and ported them conceptually back to my personal site. I will make note of these learnings as I go. It has\nalso given me a test bed to try out Gatsby upgrades before doing them at work. For example, I tried to update this site\nto Gatsby 4 and ran into a bunch of issues and will be holding off on pushing it at work until it's fixed for this site.\n\n### Is Gatsby Any Good?\n\nOh I have complicated feelings about this! Like all frameworks, Gatsby has made a lot of trade offs to accomplish it's\ngoal. **I would say that Gatsby's core goal is to be the Django/Wordpress of static site generators that is capable of\nmagical incremental builds.** Let's look at how they accomplish that goal.\n\n1. Your data store for all data while building the site is coming from GraphQL\n2. They have a very through [lifecycle API model][gatsby-lifecycle-apis] that allows for a massive amount of plugins to be added to your site with\n   minimal configuration.\n3. The data in this GraphQL store is used to intelligently determine what pages are needed to be rebuilt when building\n   a page with an established cache.\n\nThese three points are all fine on their own but, as they say, the devil is in the details. All of your data that you\nuse must be in the GraphQL store, so you must hook into the lifecycle APIs to do this, which forces this data fetching\nto be divorced from your application components. This is actually very similar to the comparison of Redux vs React\nHooks.\n\n**Furthermore, how these GraphQL queries are executed are very flawed.** Instead of running them when generating the HTML\nafter the JS has compiled, they are instead extracted with a Babel transformation and transpiled to a JSON\nfile that is read when creating the HTML. This seems like a minor distinction but it means that your GraphQL queries are\nunable to take arguments outside of the template system AND cannot be shared between components because that makes them\ntoo hard to extract.\n\nCompare this to [Next.js][nextjs], which does all of it's data querying as plain JavaScript functions that can be shared\nfreely. It doesn't provide a shared data store that all data needs to go into BUT it is able to do the same incremental\nbuilding that Gatsby does all the same. If I were to do this over, I would probably use Next.js, but I'm fine with\nGatsby for now.\n\nThat said, there are a lot of things that Gatsby does right:\n\n- It's plugin ecosystem is massive and will save you a lot of time from the jump\n- The [image component][gatsby-plugin-image] is extremely good, far better than Next.js's\n- The documentation is fantastic\n\n## Other Technologies\n\nHere's some quick hot takes on some other libraries used in this update:\n\n- [Tailwind CSS][tailwind]: I think it's a little barebones compared to other CSS frameworks. That said, it's theming\n  system and naming conventions made it easy to have consistent spacing, colors, fonts, etc. I do fear that updating\n  styles in the future will be annoying because I will have forgotten it's naming conventions, but that's Future Eli's\n  problem!\n- [Styled Components][styled-components]: I love Styled Components! It has a very intuitive API and I have become\n  a complete CSS in JS fan boi. My personal site does not have a lot of reusable components so I didn't get to use some\n  of it's more complex features, but I would be excited to in a future job!\n- [twin.macro][twin.macro]: This is the glue for Tailwind CSS to Styled Components and it's good! It allowed me to avoid\n  putting Tailwind classes right on my components, which I just could not stomach. It also makes the dev build fail when\n  you provide an invalid Tailwind class.\n- [mdx][mdx]: What cool little language! It's Markdown that you can import React components into! Such a simple concept\n  executed perfectly.\n\n# Feelings API v2\n\n<figure>\n\n![Feelings widget on my homepage](https://eligundry.com/../../assets/img/website-update/feelings-widget.png)\n\n  <figcaption>I just had to show off a little bit and add that chart</figcaption>\n</figure>\n\nLast year, [I made an API to submit diary entries from the Daylio app on my phone][feelings-api]. I am happy to announce\nthat I am continuing to use this functionality and I think posting them on my site + Twitter has encouraged me to keep\ndoing it (I'm at ~700 days in a row).\n\nIn the original version of this, I was pulling [the data from the API][feelings-api-endpoint] at runtime using [React Query][react-query]. React\nQuery is a fine library and I love using it, but this is a static site so pulling it at runtime is far slower than doing\nit at compile time.\n\nAs it turns out, moving this data into Gatsby was very easy using [gatsby-source-custom-api][gatsby-source-custom-api].\nAll I needed was the following in my `gatsby-config.js`.\n\n```javascript\n{\n  resolve: 'gatsby-source-custom-api',\n  options: {\n    url: 'https://api.eligundry.com/api/feelings',\n    rootKey: 'feelings',\n    schemas: {\n      feelings: `\n        time: String!\n        mood: String!\n        activities: [String]\n        notes: [String!]\n      `,\n    },\n  },\n},\n```\n\nThis allows me to fetch my latest diary entry like so:\n\n<GithubFile url=\"https://github.com/eligundry/eligundry.com/blob/f9aa93ddf5e384e621ebd549b87e4e72defb00e0/gatsby/src/components/Daylio/useLatestFeelings.ts\" />\n\nSuper easy! In order for this site to be built daily, I added a call to a Netlify build webhook to the API endpoint\nI upload the diary entries to. This build will also generate a dedicated RSS feed that I use IFTTT to post the entry to\nTwitter with. This, in turn, also updates all the other Gatsby sources, which allows things like my Goodreads shelves\nand Listening widgets to update naturally.\n\nSpeaking of those widgets...\n\n# Goodreads Shelves\n\n![Goodreads bookshelves on my home page](https://eligundry.com/../../assets/img/website-update/bookshelves.png)\n\nSimilar to the feelings widget, I had a Goodreads widget of what I was reading on the previous version of my website. It\nwas using a [React component to pull the data from the Goodreads API][react-goodreads-shelf]. And this was fine until\n[Goodreads announced it was sunsetting it's API][goodbye-goodreads-api]. Not to worry, I realized it was pretty easy to\npull this data from their HTML and submitted a [suggestion to this component to pull data from the HTML that was\neventually adopted][rgrs-pr]. But even then, I ran into more issues:\n\n1. Pulling this data at runtime is very slow, even slower now that I'm pulling HTML instead of (relatively) compact XML\n2. Images were at max resolution, much larger than they needed to be given the size of them on the page\n3. The component used a hosted instance of [CORS Anywhere][cors-anywhere] on Heroku that would get rate limited frequently\n\nI threw up my hands and realized that I needed to make a custom Gatsby source to pull this data at compile time. At\nfirst, I had it living as a script inside my site, but then I started making custom Gatsby source packages at work and\ndecided to extract it into my first NPM package!\n\n[npm: @eligundry/gatsby-source-goodreads](https://www.npmjs.com/package/@eligundry/gatsby-source-goodreads)\n\nOne of the added benefits of moving this to compile time is that Gatsby is able to download the cover images and plug it\ninto it's image plugin and resize the images to be small and load progressively as the page loads! It's a win win win!\nEventually, I might learn how to extract my progress reading these books and include them in this widget, but that is\nfor another day.\n\n# Listening Homepage Widget\n\n![Last.fm cover on my homepage](https://eligundry.com/../../assets/img/website-update/last-fm-cover.png)\n\nIt is very easy to pull a Last.fm collage of your top albums. A bunch of services exist for this. Why bring this up?\n\nWell, I elevated it in 2 ways:\n\n1. I'm pulling the image at build time into Gatsby so that it's image component can optimize it\n2. I'm overlaying the artist name, album and the number of scrobbles with an image map\n\nNumber 2 is the thing I want to brag about. I used [gatsby-source-lastfm][gatsby-source-lastfm] to pull my last 1000\nscrobbles and then I whipped up a React component to overlay the info onto the image using an [HTML image map][html-image-map]\n(in the year 2021, it's not often you get the chance to use an image map legitimately, so I was tickled pink to do so).\n\nThe only downside to this approach is that it's not 100% accurate. I've noticed the following things:\n\n1. There is often lag between the data that the image provider is using and what the Last.fm API returns.\n2. If I have the same amount of scrobbles for a given album, the sorting in the image `!=` the sorting in the API. I think\n   the cover providers might be sorting on an internal Last.fm ID and not the album name or artist. I do not want to\n   include the ID in this bundle so I'm fine with this being inaccurate.\n\nThis homepage widget also has my current playlist of the season. Dynamic and updates without me having to do anything\nspecial. I want to add a third widget here of the shows that I'm going to that will come from [Songkick][songkick]'s\nAPI. I had this somewhat working using their ical exports, but the data wasn't clean enough for me to happy. I have\napplied for an API key, maybe I will make a Gatsby source for this as well.\n\n# Design\n\nI am not a designer. I have tried to design things in the past and I'm just terrible at it. My brain just doesn't work\nthat way. I tried to design the previous iteration of this site and it was bad. No contrast, terrible sizing & spacing\nand it was just lacking pizzaz.\n\nGoing into this redesign, I was convinced that I was going to spend money on a template for this site. I really wanted\nto pay a premium for a good template. But, I couldn't find a paid one that was simple enough for all my pages or really\nfelt like \"me\".\n\nThat said, I did find a free [minimal blog theme][minimal-blog-theme] from [Tailwind Toolbox][tailwind-toolbox] by\n[Amrit Nagi][amrit-nagi] that I fell in love with and adapted to have the best elements from my previous site shown off\nin the best possible light. Thanks Amrit, I made sure to buy you some coffees.\n\n## Fluid Background\n\nAfter cribbing the template for this site, I was feeling good about the design, but it still needed something more to\nreally show off my personality. It took me a few weeks and some endless scrolling on Twitter before I came across this:\n\n<Tweet id=\"1455150458352381963\">\n  <blockquote>\n    <p lang=\"en\" dir=\"ltr\">\n      New Houdini stuff! ✨<br />\n      <br />\n      Magical generative patterns that rearrange themselves based on the browser\n      window 🎨\n      <br />\n      <br />A kinda different approach for responsive design...\n      <br />\n      <br />\n      Inspired by{' '}\n      <a href=\"https://twitter.com/chriscoyier?ref_src=twsrc%5Etfw\">\n        @chriscoyier\n      </a>{' '}\n      &#39;s generative grids pen 💛\n      <br />\n      <br />\n      CodePen (Desktop only + Chrome / Edge)\n      <a href=\"https://t.co/WvYqXy3Yoq\">https://t.co/WvYqXy3Yoq</a>\n      <a href=\"https://t.co/6kJCifGdbB\">pic.twitter.com/6kJCifGdbB</a>\n    </p>\n    &mdash; George Francis (@georgedoescode)\n    <a href=\"https://twitter.com/georgedoescode/status/1455150458352381963?ref_src=twsrc%5Etfw\">\n      November 1, 2021\n    </a>\n  </blockquote>\n</Tweet>\n\nThis only works natively in Chrome + Edge, but I have been able to polyfill it so it works in Safari + Firefox, thought\nit's slightly more buggy than the Chrome version.\n\nI love that it's super dynamic, very cute and fits the theme of my site perfectly! I converted it into a React component\nwith a button in the bottom right part of the screen that can regenerate the pattern. It's even responsive to my dark\nmode toggle, speaking of which...\n\n<GithubFile url=\"https://github.com/eligundry/eligundry.com/blob/0cba16c996e88b8269ce124752751325f3051a8d/gatsby/src/layout/FancyBackground.tsx\" />\n\n## Dark Theme\n\nYou have to be sensitive to users that like dark color schemes! Luckily, [TailwindCSS makes it very easy to style your\nsite for dark mode][tailwindcss-dark-mode]. With that approach, I made a React provider that provides the ability to the\nuser to toggle light to dark mode with it's default value being the result of the media query `(prefers-color-scheme: dark)`, which the OS sets in MacOS and probably Windows.\n\n# Github Integrations\n\nGithub is super flexible so I embedded some features into this site that depend on it.\n\n## Utterances\n\nYears ago, I saw a talk at Brooklyn.js where someone presented [echo-chamber-js][echo-chamber-js]. It's a joke\nJavaScript library that presents a comment form that just saves the comment to the commenter's local storage so that the\ncomment just shows up for them. The thought behind it is that comments are mainly mean or spam, you really don't want to\nread them but you have to offer them, otherwise they will email you and you really don't want that. That talk has always\nstuck with me and when it came time to implement comments for my site, I decided to adapt it for React.\n\nBut, confusingly, even to me, I decided to wire up the form to my personal API so I could capture the comments and read\nthem if I wanted. It was a pretty cool endpoint that I did a good job writing, but it really served no purpose.\n\nFast forward to earlier this year. I get an email from someone who read the [Feelings API blog post][feelings-api] and\nwanted to post kind words about it. Turns out, I had recently moved my API to a different domain but didn't update my\ncode to post to it. This reader went out of their way to tell me that it was broken and that they liked my article.\nI felt bad and decided that needed to change!\n\nI decided to switch my commenting system to [utterances][utterances]. Utterances has a really cool design: It stores all\nit's comments as Github comments in the issues tab of repos. It will automatically create issues on each page a user\nleaves a comment on and thread all replies under that. This means that a user needs a Github account to comment, which\nnarrows my audience to just developers BUT that also acts as an anti-spam mechanism and is much nicer for me to moderate\nthan Disqus.\n\n## Github File Embeds\n\nIn a previous blog post, I wanted to show a [Gist][gist] like file embed of a file from the Github main site.\nUnfortunately, Github does not provide this out of the box. Luckily, a service called [EmGithub][emgithub] provides\nexactly this. They don't have a React component for this so I made one:\n\n<GithubFile url=\"https://github.com/eligundry/eligundry.com/blob/0cba16c996e88b8269ce124752751325f3051a8d/gatsby/src/components/Embeds/GitHubFile.tsx#L40-L97\" />\n\nAlright, time for me to 🤓 nerd out! EmGithub uses a JavaScript API called [`document.write`][document.write], which is\nsomething that I didn't know about until recently (for good reason). For those that don't know, `document.write` is an\nancient JavaScript API that allows you to programatically write HTML to a document as if the document was a file like\nstream. Because of how the browser's rendering engine works, this file \"closes\" on `DOMContentLoaded` and any further\nwrites will fail or clear the page. This is completely incompatible with how React works.\n\nFortunately, lots of ads work this way and some ad tech developers created a nifty library called [postscribe][postscribe],\nwhich patches `document.write` to operate using \"normal\" DOM operations that can be called anytime. I integrated that\ninto this component and it works perfectly. It even responds to my dark theme toggle!\n\n# Git Based Last Modified Timestamps\n\n> Google uses the `<lastmod>` value if it's consistently and verifiably (for example by comparing to the last\n> modification of the page) accurate.\n>\n> \\- [Google's `sitemap.xml` documentation](https://developers.google.com/search/docs/advanced/sitemaps/build-sitemap)\n\nThis one is just for me and Google, I swear, but I really want to show it off.\n\nFor customer facing sites where you are trying to have the best Google ranking for organic traffic, it's best practice\nfor your pages to have a last modified timestamp in the `sitemap.xml` and schema.org data. This tells Google when it was\nlast updated and more frequently updated content rises to the top of the search results (in theory). This has been a big\nfocus for me at work this year as we transition from developer maintained sites to sites generated from a CMS + Gatsby.\n\nTo pull a last modified timestamp from a CMS is very easy, as it's just a database record. For a static site generated\nfrom Markdown, that timestamp is harder to pull. I could have easily added a field to my blog posts frontmatter that\nI manually updated when a blog post was updated. But, that is error prone and only works for blog posts. No, I needed\nsomething better if I wanted this.\n\nThen, I cocked my head slightly to the side and looked at this a little differently and realized: Git is a database with\nlast modified timestamps. So, I went about creating a super hacky script that hooks into Gatsby's `onCreateNode`\nlifecycle event to directly add the latest commit timestamp for any given node. This required a lot of hard coding path\nrules and is in no way open sourceable, but I really just want to tell people about it!\n\n<GithubFile url=\"https://github.com/eligundry/eligundry.com/blob/last-gatsby/gatsby/config/utils/addGitLastmodifiedToNode.ts\" />\n\n# Performance\n\nI would be remiss if I blogged all about these features and my static site without bringing up performance. This blog\npost, if nothing else, is a marketing tool for my next employer to hire me on the basis of.\n\nThe primary consumer of this site will be on desktop, on that I have a perfect 💯!\n\n![Google Page Speed Insights Desktop 100 score](https://eligundry.com/../../assets/img/website-update/desktop-100.png)\n\nSadly, on mobile, this is another story and all I have is a 71 😧\n\n![Google Page Speed Insights Mobile 71 score](https://eligundry.com/../../assets/img/website-update/mobile-71.png)\n\nThis is going to be hard to get up. The biggest issue is that I have a lot of JavaScript powering all the functionality\non my site. I can try to lazy load some stuff, but because it's statically rendered and available on first paint, it's\nnot a lot of help. The good news is that the content on my site shows up quickly and that extra JS loading doesn't\nimpact the end user as much as it would if it wasn't statically rendered.\n\nMy pet theory is that Google Page Speed Insights mobile is hard for everyone because Google's target device is\na sub-$200 phone on an African 3G connection. This is obviously the future of the internet and the benchmark we should\nbe targeting, but this is also an American's personal site tailored for other Americans, so I'm not going to lose too\nmuch sleep over this.\n\n# Conclusion\n\nI have a whole other blog post/talk to write about the resume on this site. So much content from such useless navel\ngazing! In any case, I hope you like this site and thank you for reading this novel!\n\n[lektor]: https://www.getlektor.com/\n[gatsby]: https://www.gatsbyjs.com/\n[redux-talk]: /talks/redux-hooks\n[gatsby-lifecycle-apis]: https://www.gatsbyjs.com/docs/conceptual/gatsby-lifecycle-apis/\n[nextjs]: https://nextjs.org/\n[gatsby-plugin-image]: https://www.gatsbyjs.com/plugins/gatsby-plugin-image/\n[emgithub]: https://emgithub.com/\n[postscribe]: https://github.com/krux/postscribe\n[feelings-api]: /blog/feelings-api\n[react-query]: https://react-query.tanstack.com/\n[feelings-api-endpoint]: https://api.eligundry.com/api/feelings\n[gatsby-source-custom-api]: https://www.gatsbyjs.com/plugins/gatsby-source-custom-api/\n[react-goodreads-shelf]: https://www.npmjs.com/package/react-goodreads-shelf\n[goodbye-goodreads-api]: https://www.goodreads.com/topic/show/21788520-api-deprecation\n[rgrs-pr]: https://github.com/kylekarpack/react-goodreads-shelf/issues/13#issuecomment-756871730\n[cors-anywhere]: https://github.com/Rob--W/cors-anywhere\n[tailwind]: https://tailwindcss.com/\n[styled-components]: https://styled-components.com/\n[twin.macro]: https://github.com/ben-rogerson/twin.macro\n[tailwind-toolbox]: https://www.tailwindtoolbox.com/\n[minimal-blog-theme]: https://www.tailwindtoolbox.com/templates/minimal-blog-demo.php\n[amrit-nagi]: https://twitter.com/amritnagi\n[tailwindcss-dark-mode]: https://tailwindcss.com/docs/dark-mode\n[mdx]: https://mdxjs.com/\n[echo-chamber-js]: https://github.com/tessalt/echo-chamber-js\n[utterances]: https://utteranc.es/\n[gist]: https://gist.github.com/\n[document.write]: https://developer.mozilla.org/en-US/docs/Web/API/Document/write\n[gatsby-source-lastfm]: https://www.gatsbyjs.com/plugins/gatsby-source-lastfm/\n[html-image-map]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map\n[songkick]: https://www.songkick.com/",
    "version": "1.0"
  },
  "description": "I have put a lot of work into the newest version of this site and I want to tell you about it.",
  "path": "/blog/2021-website-redesign/",
  "publishedAt": "2021-11-09T05:00:00.000Z",
  "site": "https://eligundry.com",
  "tags": [
    "code"
  ],
  "textContent": "In the age of social media and hosted blogs, personal websites don't get the love they deserve. Sure, writing developer\nbased content on a site like medium.com or dev.to will get more eyeballs on it than something you\nmake on your own, but there is something extremely satisfying in owning every pixel on a site with your name on it.\nI saw this Tweet the other day and it seemed fitting to how I think about personal sites.\n\n  \n    \n      trying this analogy thing\n      pic.twitter.com/Q9889HOF3B\n    \n    &mdash; Jane Manchun Wong (@wongmjane)\n    \n      November 4, 2021\n    \n  \n\nIf we are entering the era of Web3 (which is Soylent in this Tweet, ewww) and it's ethos is trying to revive the\ntechno-optimisim and ownership of the Web 1.0 era, what better way to embody this than with an over-engineered personal\nsite? Also, I just really love Greek salad (I live in Astoria after all, you walk outside on Ditmars and they force\nGreek salad on you).\n\nAs an aside, I find all of Web3 and blockchain stuff disgusting. I have no money in any of that stuff because I object\non environmental issues and that most of it reeks of pump and dump schemes. That said, I am happy for anything that\npushes the web pendulum back in the direction of user's owning pixels attached to their name.\n\nGuiding Principals\nDynamic enough to reflect me\nFetch data at compile time\nDo a lot and do it fast\nUpdates should be triggered by tools/apps I use instead of manual intervention\n\nTechnology\n\nWould be a technology blog if I didn't nerd out about tools before describing the cool things I did?\n\nGatsby\n\nI moved this site from [Lektor][lektor] to [Gatsby][gatsby] last year while procrastinating on writing a talk about\n[Redux][redux-talk]. At my last job, I was working almost exclusively in Python + Flask, so using a Python based\ngenerator made sense at the time. But now, at my current job, I'm working a lot more in JavaScript and I wanted my site\nto be more dynamic. I also knew that we were planning on migrating our customer facing sites to a static site generator\nin the near future and Gatsby was high up on the list of possibilities for us to use. By picking it up for my personal\nsite, I could learn how to use it beforehand and know exactly what we needed to do.\n\nTurns out that this was a fantastic bet on the future. We are porting around 10 sites at work to Gatsby and because\nI had learned the basics of Gatsby months previous on a single site, I was able to dive into the more complex topics to\nenable us to port many sites at once.\n\nWhile doing some of these more complex things in a private repo at work, I have been able to take aspects that I have\nlearned there and ported them conceptually back to my personal site. I will make note of these learnings as I go. It has\nalso given me a test bed to try out Gatsby upgrades before doing them at work. For example, I tried to update this site\nto Gatsby 4 and ran into a bunch of issues and will be holding off on pushing it at work until it's fixed for this site.\n\nIs Gatsby Any Good?\n\nOh I have complicated feelings about this! Like all frameworks, Gatsby has made a lot of trade offs to accomplish it's\ngoal. I would say that Gatsby's core goal is to be the Django/Wordpress of static site generators that is capable of\nmagical incremental builds. Let's look at how they accomplish that goal.\nYour data store for all data while building the site is coming from GraphQL\nThey have a very through [lifecycle API model][gatsby-lifecycle-apis] that allows for a massive amount of plugins to be added to your site with\n   minimal configuration.\nThe data in this GraphQL store is used to intelligently determine what pages are needed to be rebuilt when building\n   a page with an established cache.\n\nThese three points are all fine on their own but, as they say, the devil is in the details. All of your data that you\nuse must be in the GraphQL store, so you must hook into the lifecycle APIs to do this, which forces this data fetching\nto be divorced from your application components. This is actually very similar to the comparison of Redux vs React\nHooks.\n\nFurthermore, how these GraphQL queries are executed are very flawed. Instead of running them when generating the HTML\nafter the JS has compiled, they are instead extracted with a Babel transformation and transpiled to a JSON\nfile that is read when creating the HTML. This seems like a minor distinction but it means that your GraphQL queries are\nunable to take arguments outside of the template system AND cannot be shared between components because that makes them\ntoo hard to extract.\n\nCompare this to [Next.js][nextjs], which does all of it's data querying as plain JavaScript functions that can be shared\nfreely. It doesn't provide a shared data store that all data needs to go into BUT it is able to do the same incremental\nbuilding that Gatsby does all the same. If I were to do this over, I would probably use Next.js, but I'm fine with\nGatsby for now.\n\nThat said, there are a lot of things that Gatsby does right:\nIt's plugin ecosystem is massive and will save you a lot of time from the jump\nThe [image component][gatsby-plugin-image] is extremely good, far better than Next.js's\nThe documentation is fantastic\n\nOther Technologies\n\nHere's some quick hot takes on some other libraries used in this update:\n[Tailwind CSS][tailwind]: I think it's a little barebones compared to other CSS frameworks. That said, it's theming\n  system and naming conventions made it easy to have consistent spacing, colors, fonts, etc. I do fear that updating\n  styles in the future will be annoying because I will have forgotten it's naming conventions, but that's Future Eli's\n  problem!\n[Styled Components][styled-components]: I love Styled Components! It has a very intuitive API and I have become\n  a complete CSS in JS fan boi. My personal site does not have a lot of reusable components so I didn't get to use some\n  of it's more complex features, but I would be excited to in a future job!\n[twin.macro][twin.macro]: This is the glue for Tailwind CSS to Styled Components and it's good! It allowed me to avoid\n  putting Tailwind classes right on my components, which I just could not stomach. It also makes the dev build fail when\n  you provide an invalid Tailwind class.\n[mdx][mdx]: What cool little language! It's Markdown that you can import React components into! Such a simple concept\n  executed perfectly.\n\nFeelings API v2\n\n  I just had to show off a little bit and add that chart\n\nLast year, [I made an API to submit diary entries from the Daylio app on my phone][feelings-api]. I am happy to announce\nthat I am continuing to use this functionality and I think posting them on my site + Twitter has encouraged me to keep\ndoing it (I'm at ~700 days in a row).\n\nIn the original version of this, I was pulling [the data from the API][feelings-api-endpoint] at runtime using [React Query][react-query]. React\nQuery is a fine library and I love using it, but this is a static site so pulling it at runtime is far slower than doing\nit at compile time.\n\nAs it turns out, moving this data into Gatsby was very easy using [gatsby-source-custom-api][gatsby-source-custom-api].\nAll I needed was the following in my .\n\nThis allows me to fetch my latest diary entry like so:\n\nSuper easy! In order for this site to be built daily, I added a call to a Netlify build webhook to the API endpoint\nI upload the diary entries to. This build will also generate a dedicated RSS feed that I use IFTTT to post the entry to\nTwitter with. This, in turn, also updates all the other Gatsby sources, which allows things like my Goodreads shelves\nand Listening widgets to update naturally.\n\nSpeaking of those widgets...\n\nGoodreads Shelves\n\nSimilar to the feelings widget, I had a Goodreads widget of what I was reading on the previous version of my website. It\nwas using a [React component to pull the data from the Goodreads API][react-goodreads-shelf]. And this was fine until\n[Goodreads announced it was sunsetting it's API][goodbye-goodreads-api]. Not to worry, I realized it was pretty easy to\npull this data from their HTML and submitted a [suggestion to this component to pull data from the HTML that was\neventually adopted][rgrs-pr]. But even then, I ran into more issues:\nPulling this data at runtime is very slow, even slower now that I'm pulling HTML instead of (relatively) compact XML\nImages were at max resolution, much larger than they needed to be given the size of them on the page\nThe component used a hosted instance of [CORS Anywhere][cors-anywhere] on Heroku that would get rate limited frequently\n\nI threw up my hands and realized that I needed to make a custom Gatsby source to pull this data at compile time. At\nfirst, I had it living as a script inside my site, but then I started making custom Gatsby source packages at work and\ndecided to extract it into my first NPM package!\n\nnpm: @eligundry/gatsby-source-goodreads\n\nOne of the added benefits of moving this to compile time is that Gatsby is able to download the cover images and plug it\ninto it's image plugin and resize the images to be small and load progressively as the page loads! It's a win win win!\nEventually, I might learn how to extract my progress reading these books and include them in this widget, but that is\nfor another day.\n\nListening Homepage Widget\n\nIt is very easy to pull a Last.fm collage of your top albums. A bunch of services exist for this. Why bring this up?\n\nWell, I elevated it in 2 ways:\nI'm pulling the image at build time into Gatsby so that it's image component can optimize it\nI'm overlaying the artist name, album and the number of scrobbles with an image map\n\nNumber 2 is the thing I want to brag about. I used [gatsby-source-lastfm][gatsby-source-lastfm] to pull my last 1000\nscrobbles and then I whipped up a React component to overlay the info onto the image using an [HTML image map][html-image-map]\n(in the year 2021, it's not often you get the chance to use an image map legitimately, so I was tickled pink to do so).\n\nThe only downside to this approach is that it's not 100% accurate. I've noticed the following things:\nThere is often lag between the data that the image provider is using and what the Last.fm API returns.\nIf I have the same amount of scrobbles for a given album, the sorting in the image  the sorting in the API. I think\n   the cover providers might be sorting on an internal Last.fm ID and not the album name or artist. I do not want to\n   include the ID in this bundle so I'm fine with this being inaccurate.\n\nThis homepage widget also has my current playlist of the season. Dynamic and updates without me having to do anything\nspecial. I want to add a third widget here of the shows that I'm going to that will come from [Songkick][songkick]'s\nAPI. I had this somewhat working using their ical exports, but the data wasn't clean enough for me to happy. I have\napplied for an API key, maybe I will make a Gatsby source for this as well.\n\nDesign\n\nI am not a designer. I have tried to design things in the past and I'm just terrible at it. My brain just doesn't work\nthat way. I tried to design the previous iteration of this site and it was bad. No contrast, terrible sizing & spacing\nand it was just lacking pizzaz.\n\nGoing into this redesign, I was convinced that I was going to spend money on a template for this site. I really wanted\nto pay a premium for a good template. But, I couldn't find a paid one that was simple enough for all my pages or really\nfelt like \"me\".\n\nThat said, I did find a free [minimal blog theme][minimal-blog-theme] from [Tailwind Toolbox][tailwind-toolbox] by\n[Amrit Nagi][amrit-nagi] that I fell in love with and adapted to have the best elements from my previous site shown off\nin the best possible light. Thanks Amrit, I made sure to buy you some coffees.\n\nFluid Background\n\nAfter cribbing the template for this site, I was feeling good about the design, but it still needed something more to\nreally show off my personality. It took me a few weeks and some endless scrolling on Twitter before I came across this:\n\n  \n    \n      New Houdini stuff! ✨\n      \n      Magical generative patterns that rearrange themselves based on the browser\n      window 🎨\n      \n      A kinda different approach for responsive design...\n      \n      \n      Inspired by{' '}\n      \n        @chriscoyier\n      {' '}\n      &#39;s generative grids pen 💛\n      \n      \n      CodePen (Desktop only + Chrome / Edge)\n      https://t.co/WvYqXy3Yoq\n      pic.twitter.com/6kJCifGdbB\n    \n    &mdash; George Francis (@georgedoescode)\n    \n      November 1, 2021\n    \n  \n\nThis only works natively in Chrome + Edge, but I have been able to polyfill it so it works in Safari + Firefox, thought\nit's slightly more buggy than the Chrome version.\n\nI love that it's super dynamic, very cute and fits the theme of my site perfectly! I converted it into a React component\nwith a button in the bottom right part of the screen that can regenerate the pattern. It's even responsive to my dark\nmode toggle, speaking of which...\n\nDark Theme\n\nYou have to be sensitive to users that like dark color schemes! Luckily, [TailwindCSS makes it very easy to style your\nsite for dark mode][tailwindcss-dark-mode]. With that approach, I made a React provider that provides the ability to the\nuser to toggle light to dark mode with it's default value being the result of the media query , which the OS sets in MacOS and probably Windows.\n\nGithub Integrations\n\nGithub is super flexible so I embedded some features into this site that depend on it.\n\nUtterances\n\nYears ago, I saw a talk at Brooklyn.js where someone presented [echo-chamber-js][echo-chamber-js]. It's a joke\nJavaScript library that presents a comment form that just saves the comment to the commenter's local storage so that the\ncomment just shows up for them. The thought behind it is that comments are mainly mean or spam, you really don't want to\nread them but you have to offer them, otherwise they will email you and you really don't want that. That talk has always\nstuck with me and when it came time to implement comments for my site, I decided to adapt it for React.\n\nBut, confusingly, even to me, I decided to wire up the form to my personal API so I could capture the comments and read\nthem if I wanted. It was a pretty cool endpoint that I did a good job writing, but it really served no purpose.\n\nFast forward to earlier this year. I get an email from someone who read the [Feelings API blog post][feelings-api] and\nwanted to post kind words about it. Turns out, I had recently moved my API to a different domain but didn't update my\ncode to post to it. This reader went out of their way to tell me that it was broken and that they liked my article.\nI felt bad and decided that needed to change!\n\nI decided to switch my commenting system to [utterances][utterances]. Utterances has a really cool design: It stores all\nit's comments as Github comments in the issues tab of repos. It will automatically create issues on each page a user\nleaves a comment on and thread all replies under that. This means that a user needs a Github account to comment, which\nnarrows my audience to just developers BUT that also acts as an anti-spam mechanism and is much nicer for me to moderate\nthan Disqus.\n\nGithub File Embeds\n\nIn a previous blog post, I wanted to show a [Gist][gist] like file embed of a file from the Github main site.\nUnfortunately, Github does not provide this out of the box. Luckily, a service called [EmGithub][emgithub] provides\nexactly this. They don't have a React component for this so I made one:\n\nAlright, time for me to 🤓 nerd out! EmGithub uses a JavaScript API called [][document.write], which is\nsomething that I didn't know about until recently (for good reason). For those that don't know,  is an\nancient JavaScript API that allows you to programatically write HTML to a document as if the document was a file like\nstream. Because of how the browser's rendering engine works, this file \"closes\" on  and any further\nwrites will fail or clear the page. This is completely incompatible with how React works.\n\nFortunately, lots of ads work this way and some ad tech developers created a nifty library called [postscribe][postscribe],\nwhich patches  to operate using \"normal\" DOM operations that can be called anytime. I integrated that\ninto this component and it works perfectly. It even responds to my dark theme toggle!\n\nGit Based Last Modified Timestamps\n\nGoogle uses the  value if it's consistently and verifiably (for example by comparing to the last\nmodification of the page) accurate.\n\\- Google's  documentation\n\nThis one is just for me and Google, I swear, but I really want to show it off.\n\nFor customer facing sites where you are trying to have the best Google ranking for organic traffic, it's best practice\nfor your pages to have a last modified timestamp in the  and schema.org data. This tells Google when it was\nlast updated and more frequently updated content rises to the top of the search results (in theory). This has been a big\nfocus for me at work this year as we transition from developer maintained sites to sites generated from a CMS + Gatsby.\n\nTo pull a last modified timestamp from a CMS is very easy, as it's just a database record. For a static site generated\nfrom Markdown, that timestamp is harder to pull. I could have easily added a field to my blog posts frontmatter that\nI manually updated when a blog post was updated. But, that is error prone and only works for blog posts. No, I needed\nsomething better if I wanted this.\n\nThen, I cocked my head slightly to the side and looked at this a little differently and realized: Git is a database with\nlast modified timestamps. So, I went about creating a super hacky script that hooks into Gatsby's \nlifecycle event to directly add the latest commit timestamp for any given node. This required a lot of hard coding path\nrules and is in no way open sourceable, but I really just want to tell people about it!\n\nPerformance\n\nI would be remiss if I blogged all about these features and my static site without bringing up performance. This blog\npost, if nothing else, is a marketing tool for my next employer to hire me on the basis of.\n\nThe primary consumer of this site will be on desktop, on that I have a perfect 💯!\n\nSadly, on mobile, this is another story and all I have is a 71 😧\n\nThis is going to be hard to get up. The biggest issue is that I have a lot of JavaScript powering all the functionality\non my site. I can try to lazy load some stuff, but because it's statically rendered and available on first paint, it's\nnot a lot of help. The good news is that the content on my site shows up quickly and that extra JS loading doesn't\nimpact the end user as much as it would if it wasn't statically rendered.\n\nMy pet theory is that Google Page Speed Insights mobile is hard for everyone because Google's target device is\na sub-$200 phone on an African 3G connection. This is obviously the future of the internet and the benchmark we should\nbe targeting, but this is also an American's personal site tailored for other Americans, so I'm not going to lose too\nmuch sleep over this.\n\nConclusion\n\nI have a whole other blog post/talk to write about the resume on this site. So much content from such useless navel\ngazing! In any case, I hope you like this site and thank you for reading this novel!\n\n[lektor]: https://www.getlektor.com/\n[gatsby]: https://www.gatsbyjs.com/\n[redux-talk]: /talks/redux-hooks\n[gatsby-lifecycle-apis]: https://www.gatsbyjs.com/docs/conceptual/gatsby-lifecycle-apis/\n[nextjs]: https://nextjs.org/\n[gatsby-plugin-image]: https://www.gatsbyjs.com/plugins/gatsby-plugin-image/\n[emgithub]: https://emgithub.com/\n[postscribe]: https://github.com/krux/postscribe\n[feelings-api]: /blog/feelings-api\n[react-query]: https://react-query.tanstack.com/\n[feelings-api-endpoint]: https://api.eligundry.com/api/feelings\n[gatsby-source-custom-api]: https://www.gatsbyjs.com/plugins/gatsby-source-custom-api/\n[react-goodreads-shelf]: https://www.npmjs.com/package/react-goodreads-shelf\n[goodbye-goodreads-api]: https://www.goodreads.com/topic/show/21788520-api-deprecation\n[rgrs-pr]: https://github.com/kylekarpack/react-goodreads-shelf/issues/13#issuecomment-756871730\n[cors-anywhere]: https://github.com/Rob--W/cors-anywhere\n[tailwind]: https://tailwindcss.com/\n[styled-components]: https://styled-components.com/\n[twin.macro]: https://github.com/ben-rogerson/twin.macro\n[tailwind-toolbox]: https://www.tailwindtoolbox.com/\n[minimal-blog-theme]: https://www.tailwindtoolbox.com/templates/minimal-blog-demo.php\n[amrit-nagi]: https://twitter.com/amritnagi\n[tailwindcss-dark-mode]: https://tailwindcss.com/docs/dark-mode\n[mdx]: https://mdxjs.com/\n[echo-chamber-js]: https://github.com/tessalt/echo-chamber-js\n[utterances]: https://utteranc.es/\n[gist]: https://gist.github.com/\n[document.write]: https://developer.mozilla.org/en-US/docs/Web/API/Document/write\n[gatsby-source-lastfm]: https://www.gatsbyjs.com/plugins/gatsby-source-lastfm/\n[html-image-map]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map\n[songkick]: https://www.songkick.com/",
  "title": "Can I Brag About This Site For A Second?",
  "updatedAt": "2025-02-12T02:34:47.000Z"
}