<?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: Steven Woodson</title>
    <description>The latest articles on DEV Community by Steven Woodson (@stevenwoodson).</description>
    <link>https://dev.to/stevenwoodson</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%2F1021753%2Fe364c9b3-6620-4feb-bce4-321afaa8a604.jpeg</url>
      <title>DEV Community: Steven Woodson</title>
      <link>https://dev.to/stevenwoodson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stevenwoodson"/>
    <language>en</language>
    <item>
      <title>Web Components in Astro</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Wed, 29 Nov 2023 12:00:00 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/web-components-in-astro-4n2k</link>
      <guid>https://dev.to/stevenwoodson/web-components-in-astro-4n2k</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F11%2FWeb-Components-in-Astro.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F11%2FWeb-Components-in-Astro.webp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve recently been jumping into learning more about &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, using a personal project that needs to be replatformed as an excuse to give it a try. The first thing I wanted to dive into and learn more about was how components are handled, and especially how well it plays with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Components" rel="noopener noreferrer"&gt;browser native Web Components&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I burned &lt;em&gt;a lot&lt;/em&gt; of time here just trying to understand how a web component could be copied into an Astro project. So instead of waiting to blog about the whole process, I wanted to share my learnings about components separately first.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Attempt – AstroHeart
&lt;/h2&gt;

&lt;p&gt;My first attempt was to get the &lt;code&gt;AstroHeart&lt;/code&gt; example in the &lt;a href="https://docs.astro.build/en/guides/client-side-scripts/#web-components-with-custom-elements" rel="noopener noreferrer"&gt;Web components with custom elements&lt;/a&gt; section of the Scripts and Event Handling documentation to work.&lt;/p&gt;

&lt;p&gt;I got it working when the whole code block was placed in a &lt;a href="https://docs.astro.build/en/core-concepts/layouts/" rel="noopener noreferrer"&gt;layout&lt;/a&gt; or a &lt;a href="https://docs.astro.build/en/core-concepts/astro-pages/" rel="noopener noreferrer"&gt;page&lt;/a&gt;, but not when it was a referenced component from the &lt;code&gt;/components/&lt;/code&gt; directory. I think we can all agree that adding web components to a page or layout would be categorized as a “Bad Idea”.&lt;/p&gt;

&lt;p&gt;After much fiddling I came to the conclusion that an Astro component that then instantiates a native web component is the best I could manage. It feels weird, but it works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackblitz.com/edit/github-mdsq7h-1r8rqx?file=src%2Fpages%2Findex.astro" rel="noopener noreferrer"&gt;Here’s the full minimal example of AstroHeart working as a web component wrapped with an Astro component on Stackblitz&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second Web Component Attempt – Sidebar
&lt;/h2&gt;

&lt;p&gt;Now that I know it’s possible, I wanted to ramp things up with an actually usable -and slightly more complicated – example. The first web component I needed to port over for my project happened to be &lt;a href="https://every-layout.dev/layouts/sidebar/#the-component" rel="noopener noreferrer"&gt;the excellent Sidebar from the Every Layout project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A couple new issues emerged in this process of adding it, here’s how I got passed them both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing Props
&lt;/h3&gt;

&lt;p&gt;Welp, the Every Layout Sidebar has five separate attributes that can be added to the &lt;code&gt;&amp;lt;sidebar-l&amp;gt;&lt;/code&gt; custom element and I quickly realized I need to manage that prop handshake between Astro and the native web component.&lt;/p&gt;

&lt;p&gt;First, I went with the most obvious solution of gathering all props and manually passing them in, something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
const { side, sideWidth, contentMin, space, noStretch } = Astro.props;
---

&amp;lt;sidebar-l
  side={side}
  sideWidth={sideWidth}
  contentMin={contentMin}
  space={space}
  noStretch={noStretch}
&amp;gt;
  &amp;lt;slot /&amp;gt;
&amp;lt;/sidebar-l&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that so verbose and error prone, there’s gotta be a better way right? Turns out, there is! Replace that code above with this and you’re all set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;sidebar-l {...Astro.props}&amp;gt;
  &amp;lt;slot /&amp;gt;
&amp;lt;/sidebar-l&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still a little weird but much better, right? Now if names change or more props are added I don’t have to keep going back to update this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scoped vs Global Styles
&lt;/h3&gt;

&lt;p&gt;When styles are added to the Astro component using &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags, they’re automatically scoped. In some cases, as is the case for this Sidebar component with several modification options, this can be less desirable.&lt;/p&gt;

&lt;p&gt;I found this out first hand when I was attempting to adjust the spacing between the sidebar and the main content area using &lt;code&gt;&amp;lt;Sidebar space="var(--space-m)"&amp;gt;&lt;/code&gt;. I came to realize that the default styles were of a higher specificity than the modified styles applied via that &lt;code&gt;space&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;In cases like this, drop a &lt;code&gt;is:global&lt;/code&gt; into the opening style tag like this, &lt;code&gt;&amp;lt;style is:global&amp;gt;&lt;/code&gt; and it’ll be treated as a global style where the cascade for modifications will work.&lt;/p&gt;

&lt;p&gt;I suppose you could also move the component styles to where the rest of your global styles are located, but you lose that encapsulation of everything being in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live example
&lt;/h3&gt;

&lt;p&gt;I’ve worked up &lt;a href="https://stackblitz.com/edit/github-mdsq7h-sa7py9?file=src%2Fcomponents%2FSidebar.astro" rel="noopener noreferrer"&gt;another Stackblitz for this Sidebar component example&lt;/a&gt; too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third Attempt – Auto-import Multiple Components
&lt;/h2&gt;

&lt;p&gt;I still felt a bit odd with wrapping web components inside Astro components, even though I was able to overcome any blockers that emerged. I think it’s because having to manually Astro-ify every component I wanted to use feels like it goes against the portability of native web components.&lt;/p&gt;

&lt;h3&gt;
  
  
  So, I tried again.
&lt;/h3&gt;

&lt;p&gt;With a bit more encouragement from &lt;a href="https://daniel-saunders.com/" rel="noopener noreferrer"&gt;Daniel Saunders&lt;/a&gt;, who reminded me that all it really takes is an import of the web component somewhere in the page, I set my sights on trying that.&lt;/p&gt;

&lt;p&gt;As a quick proof of concept, I grabbed another of the free Every Layout components – &lt;a href="https://every-layout.dev/layouts/stack/" rel="noopener noreferrer"&gt;the Stack&lt;/a&gt; – and built up a minimally viable page with the following. The references to &lt;code&gt;web-components&lt;/code&gt; is because I wanted another separate directory under &lt;code&gt;src&lt;/code&gt; to keep Astro components separate from web components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;stack-l space="3rem"&amp;gt;
  &amp;lt;h2&amp;gt;H2 headline&amp;lt;/h2&amp;gt;
  &amp;lt;stack-l space="1.5rem"&amp;gt;
    &amp;lt;p&amp;gt;
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi aperiam,
      cupiditate qui totam incidunt ipsum dolores.
    &amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi aperiam,
      cupiditate qui totam incidunt ipsum dolores.
    &amp;lt;/p&amp;gt;
  &amp;lt;/stack-l&amp;gt;
  &amp;lt;h2&amp;gt;H2 headline&amp;lt;/h2&amp;gt;

&amp;lt;/stack-l&amp;gt;
&amp;lt;script&amp;gt;
  import '../web-components/Stack/Stack.js';
  import '../web-components/Stack/Stack.css';
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sure enough, that worked just fine!&lt;/p&gt;

&lt;h3&gt;
  
  
  Automating imports
&lt;/h3&gt;

&lt;p&gt;But, adding a script tag with imports of the (potentially double digit) JS and CSS web component files I’d need to use on every page doesn’t sound like fun to me. Instead, I set my sights on auto-importing based on a glob pattern.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://docs.astro.build/en/reference/api-reference/#astroglob" rel="noopener noreferrer"&gt;Astro.glob()&lt;/a&gt; as that sounds like just what I need. I realized that what I was doing wasn’t going to work though, because I was attempting the import in the front matter which meant this was trying to import on the server side where &lt;code&gt;HTMLElement&lt;/code&gt; isn’t defined.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F11%2FScreen-Shot-2023-11-21-at-7.25.22-AM-1024x606.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F11%2FScreen-Shot-2023-11-21-at-7.25.22-AM-1024x606.png" alt="Error message from Astro with the following details. An error occurred. HTMLElement is not defined in Stack/Stack.js on line 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remembering that it needs to be in a script tag within the content of the page rather than in the front matter got me away from that error, but it still didn’t work.&lt;/p&gt;

&lt;p&gt;I read up on what &lt;code&gt;Astro.glob()&lt;/code&gt; is doing, it’s a wrapper for &lt;a href="https://vitejs.dev/guide/features.html#glob-import" rel="noopener noreferrer"&gt;Vite’s Glob Import&lt;/a&gt; (&lt;code&gt;import.meta.glob&lt;/code&gt;). That’s set up by default to lazy load which meant that it wasn’t going to end up on the page because I wasn’t using it on the server side. From the docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Matched files are by default lazy-loaded via dynamic import and will be split into separate chunks during build. If you’d rather import all the modules directly (e.g. relying on side-effects in these modules to be applied first), you can pass &lt;code&gt;{ eager: true }&lt;/code&gt; as the second argument&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once I did just that, I got it working!&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-importing at the Layout Level
&lt;/h3&gt;

&lt;p&gt;One more change now that I had something functional, instead of doing this import on the page I moved it to the bottom of my global layout file.&lt;/p&gt;

&lt;p&gt;Now, I have access to all web components on all pages that use this layout. Here’s the final code for importing everything, placed in my layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  const webComponentsCSS = await import.meta.glob(
    '../web-components/**/*.css',
    { eager: true }
  );
  const webComponentsJS = await import.meta.glob(
    '../web-components/**/*.js',
    { eager: true }
  );
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added a second web component to my setup to make sure it was importing both, and sure enough it did! Rather than pasting all that code here, scroll down to the Live example link below to see it all isolated in another Stackblitz.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Implications
&lt;/h3&gt;

&lt;p&gt;Because I’m still learning about all this, and the project I have in mind to use it is very small, I’ve not delved too deep into the implications of importing all web components in this way. I did run a production build to see how everything came together, it appears that all components are compiled into one &lt;code&gt;hoisted.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;There’s likely some consideration needed for very large applications, because I’d bet the compiled source wouldn’t be ideal for projects of any real complexity. The first thing I’d try is bundling web components into sub directories and using this glob import pattern in smaller chunks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live example
&lt;/h3&gt;

&lt;p&gt;Once again, &lt;a href="https://stackblitz.com/edit/github-mdsq7h-9whase?file=src%2Fpages%2Findex.astro" rel="noopener noreferrer"&gt;I made this a minimally reproducible Stackblitz of two native web components&lt;/a&gt; being used on a page after being auto imported in the parent layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That’s where I’m at as of November 2023, I’m pretty happy with the auto-importing method noted in that third attempt above. But I am still very interested in hearing about other ways to do this, and especially about how it affects page performance. If you’ve got other methods to make this work, I’d love to see some examples!&lt;/p&gt;

&lt;p&gt;I’ve already updated this post with the third example after some more fiddling, if I figure out or hear of any further updates I’ll be sure to keep this post updated.&lt;/p&gt;

&lt;p&gt;I’m going to continue forward with porting a whole site to Astro, so expect to see another post soon about the rest of the process!&lt;/p&gt;

</description>
      <category>astro</category>
      <category>components</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>Eleventy Style Guide Generator – Step by Step guide adding to an existing site</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Fri, 25 Aug 2023 21:03:30 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/eleventy-style-guide-generator-step-by-step-guide-adding-to-an-existing-site-1629</link>
      <guid>https://dev.to/stevenwoodson/eleventy-style-guide-generator-step-by-step-guide-adding-to-an-existing-site-1629</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6h311BFQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/08/Eleventy-Style-Guide-Generator-Step-by-Step-guide-to-add-to-an-existing-site-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6h311BFQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/08/Eleventy-Style-Guide-Generator-Step-by-Step-guide-to-add-to-an-existing-site-1.png" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I shared the &lt;a href="https://stevenwoodson.com/blog/eleventy-style-guide-generator-with-webc-component-support/"&gt;Eleventy Style Guide Generator&lt;/a&gt; (&lt;a href="https://github.com/stevenwoodson/11ty-design-system"&gt;source is on GitHub&lt;/a&gt; and &lt;a href="https://stevenwoodson.github.io/11ty-design-system/"&gt;here’s the demo site&lt;/a&gt; it generates) a few weeks ago. Because it’s a full Eleventy repo and not a plugin (can’t be until &lt;a href="https://github.com/11ty/eleventy/issues/1612"&gt;Virtual Templates&lt;/a&gt; are supported), I received a few questions about its requirements including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are design tokens required?&lt;/li&gt;
&lt;li&gt;Is the use of WebC required?&lt;/li&gt;
&lt;li&gt;Can I use a CSS preprocessor like SCSS?&lt;/li&gt;
&lt;li&gt;How do I add this to an existing Eleventy site?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post attempts to answer these questions, I’ll also be updating the repo with more of these details as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are design tokens required?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No, you don’t need design tokens to use this generator.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you decide not to use design tokens you can also remove some of the additional dependencies and files including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Tailwind dependency &amp;amp; configuration file at `tailwind.config.js. All it’s doing is converting the JSON sourced design tokens into usable CSS custom properties.&lt;/li&gt;
&lt;li&gt;the two CSS utilities at &lt;code&gt;src/_utilities/css-utils&lt;/code&gt;. Similarly, they’re only being used by the Tailwind configuration to generate the custom properties.&lt;/li&gt;
&lt;li&gt;the tokens static design system page at &lt;code&gt;src/design-system/Atoms/tokens.njk&lt;/code&gt;. It’s only there to itemize the individual design tokens.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is the use of WebC required?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Not technically, but it’s highly encouraged.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WebC brings first-class components to Eleventy, this being a way to itemize components in a style guide it stands to reason that the two are well suited as a pair.&lt;/p&gt;

&lt;p&gt;If you choose not to use WebC, or need to ease into supporting it in your existing project, here’s a few considerations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Naming Collisions
&lt;/h3&gt;

&lt;p&gt;The generator is assuming that each component under &lt;code&gt;src/_includes/components&lt;/code&gt; has a &lt;code&gt;.webc&lt;/code&gt; component and an &lt;code&gt;.njk&lt;/code&gt; example defined. If your components are also going to be in Nunjucks you’ll need another qualifier added to example files so you can programmatically tell them apart.&lt;/p&gt;

&lt;p&gt;Something like &lt;code&gt;.stories.njk&lt;/code&gt; (like Storybook) would work fine. Then you’d update &lt;code&gt;src/design-system/components-pages.njk&lt;/code&gt; to include that extra qualifier, for example like this&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
{%- macro c(path, name, params) -%}&lt;br&gt;
  {% include "components/" + path + "/" + name + ".stories.njk" ignore missing %}&lt;br&gt;
{%- endmacro -%}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Layouts
&lt;/h3&gt;

&lt;p&gt;There are also two WebC layouts that are being used to generate the style guide files at &lt;code&gt;src/_includes/layouts/ds-static.webc&lt;/code&gt; and &lt;code&gt;src/_includes/layouts/ds-wrapper.webc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You could enable WebC just for new pages and templates like these, but it should be fairly straightforward to convert into another template language if you don’t want to enable WebC at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Existing components
&lt;/h3&gt;

&lt;p&gt;There are a handful of components defined in the repo and they’re all WebC based components, so you won’t (easily) be able to make use of them.&lt;/p&gt;

&lt;p&gt;You could convert them to another template language but that effort may not be worthwhile depending on your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I use a CSS preprocessor like SCSS?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sure, you can still use a preprocessor like LESS, SCSS, or Stylus&lt;/strong&gt; if that fits better in your workflow.&lt;/p&gt;

&lt;p&gt;I had decided on using only CSS to reduce the number of dependencies required, that’s all.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I add this to an existing Eleventy site?
&lt;/h2&gt;

&lt;p&gt;I jotting down what I’ve needed to do to add this to my own site, and listed it all here.&lt;/p&gt;

&lt;p&gt;This including everything that I had noted above as optional, so I’ve split it up into sections so you can skip the bits you don’t want.&lt;/p&gt;

&lt;p&gt;File path references directly relate to &lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main/src"&gt;the paths in the 11ty-design-system repo&lt;/a&gt; starting with &lt;code&gt;src&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Essentials Setup
&lt;/h3&gt;

&lt;p&gt;First, you need to determine where you want the style guide to live, in the repo I’ve set this as &lt;code&gt;/design-system&lt;/code&gt; but you can place it anywhere that makes sense for your project.&lt;/p&gt;

&lt;h4&gt;
  
  
  Copy the following pages to this new folder
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/design-system/components-pages.njk&lt;/code&gt; – generates the style guide page for each component, with tabs for Demo, HTML, and Context. Loads the demo as an iframe of…&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/design-system/components-full-pages.njk&lt;/code&gt; – this is the isolated component in a blank page. Iframed into the component page above.

&lt;ul&gt;
&lt;li&gt;This is relying on a “base” layout too, if yours is in a different location you’ll need to update &lt;code&gt;layout: layouts/base.webc&lt;/code&gt; as well.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/design-system/index.md&lt;/code&gt; – The style guide homepage, feel free to replace the content here (and even change the template language).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/design-system/Atoms&lt;/code&gt; – This folder contains files that itemize the design system tokens and most major native HTML elements&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Copy the following to your layouts directory
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/_includes/layouts/ds-wrapper.webc&lt;/code&gt; – The layout of the design system pages, including the sidebar menu.

&lt;ul&gt;
&lt;li&gt;This is relying on a “base” layout too, if yours is in a different location you’ll need to update &lt;code&gt;layout: layouts/base.webc&lt;/code&gt; as well.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/_includes/layouts/ds-static.webc&lt;/code&gt; – The layout for any static content pages you add in addition to the auto-generated ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Copy the following into your &lt;code&gt;.eleventy.js&lt;/code&gt; config
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
/* Shortcodes */&lt;br&gt;
config.addPairedShortcode('brace', function (content, type = 'curly') {&lt;br&gt;
  const [opening, closing] = {&lt;br&gt;
    curly: ['{{', '}}'],&lt;br&gt;
    silent: ['{%-', '-%}']&lt;br&gt;
  }[type];&lt;br&gt;
  return&lt;/code&gt;${opening}${content}${closing}`;&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;config.addPairedShortcode('prettify', (content) =&amp;gt; {&lt;br&gt;
  return prettify(content);&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;/* Filters */&lt;br&gt;
config.addFilter('console', function (value) {&lt;br&gt;
  return JSON.stringify(value, null, 2);&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;/* Custom Collections &lt;em&gt;/&lt;br&gt;
// Filter source file names using a glob&lt;br&gt;
config.addCollection('dsAtoms', function (collectionApi) {&lt;br&gt;
  return collectionApi.getFilteredByGlob(' *&lt;/em&gt;/design-system/Atoms/** /*');&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Descriptions in the same order as the code above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Brace &amp;amp; Prettify shortcodes, for help displaying content in the Code and Context tab panels&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;console&lt;/code&gt; filter for stringifying the JSON configuration in the Context tab&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dsAtoms&lt;/code&gt; custom collection so we can also add the static pages under the &lt;code&gt;Atoms&lt;/code&gt; directory to the style guide menu&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Copy the foundational CSS &amp;amp; JS
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/assets/css/design-system.css&lt;/code&gt; – helps with setting up design system specific styles like color swatches. Everything is prefixed with &lt;code&gt;ds-&lt;/code&gt; to avoid CSS collisions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/assets/js/vendor/seven-minute-tabs.js&lt;/code&gt; – Powers the Demo, HTML, and Context tab structure for the component style guide pages&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Copy the following data files
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/_data/components.js&lt;/code&gt; – generates all the components as a collection, be sure to update the folder paths here too if they’re different in your setup.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/_data/global.js&lt;/code&gt; – defines the &lt;code&gt;random&lt;/code&gt; function used in some templates to cache bust the CSS.

&lt;ul&gt;
&lt;li&gt;If you have a cache busting filter in place you can skip this one and update references to &lt;code&gt;global.random()&lt;/code&gt; with that instead.&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;With these steps I had a functional style guide being generated. You can see my progress at &lt;a href="https://stevenwoodson.com/design-system/"&gt;https://stevenwoodson.com/design-system/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3lx0Wa5N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/08/Screen-Shot-2023-08-25-at-8.37.55-AM-1024x732.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3lx0Wa5N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/08/Screen-Shot-2023-08-25-at-8.37.55-AM-1024x732.png" alt="Screenshot of the design system open to the design tokens page. The top of the page lists the color tokens starting with background, and then listing text and grey shades." width="800" height="572"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot of the Design Tokens design system page after following the steps in this section to get it added to my site.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Design Tokens
&lt;/h3&gt;

&lt;p&gt;Here’s what you need to copy to get design tokens set up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tailwind.config.js&lt;/code&gt; – This is what powers the generation of all the CSS custom properties&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postcss.config.js&lt;/code&gt; – PostCSS triggers the tailwind configuration, it also minifies the CSS&lt;/li&gt;
&lt;li&gt;Build step for the above in your package.json, &lt;code&gt;"css": "npx postcss src/assets/css/*.css --base src --dir dist",&lt;/code&gt; for example&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/_data/designTokens&lt;/code&gt; – This is where all the design token JSON files are stored, they’re in the data directory to make it easier to parse that data with Eleventy’s automated data cascade &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/_utilities/css-utils&lt;/code&gt; – Utility classes that the tailwind config needs in order to generate the custom properties and the clamp values that many of them need&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The act of utilizing the design tokens is likely another post entirely, but the TL;DR of it is that you’re going to need to comb through your CSS and replace color, spacing, and other tokenized values with those coming from this generator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding WebC Support &amp;amp; Components
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Adding WebC support
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You’re going to &lt;strong&gt;need Eleventy 2.0.0 or newer&lt;/strong&gt; to be able to utilize WebC. I was on an older 1.x version and was surprised at how easy the upgrade process was. Hoping you have similar luck if you need to upgrade as well.&lt;/li&gt;
&lt;li&gt;Follow the &lt;a href="https://www.11ty.dev/docs/languages/webc/#installation"&gt;install steps from the official WebC docs&lt;/a&gt;, currently it’s not bundled with core so you’ll need to perform an NPM install and add the plugin to your config.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  WebC Component Structure
&lt;/h4&gt;

&lt;p&gt;All components are to be stored under &lt;code&gt;src/_includes/components&lt;/code&gt;. One component per folder is the ideal and you can nest folders down to three levels currently.&lt;/p&gt;

&lt;p&gt;Here’s the structure of a typical component folder, using &lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main/src/_includes/components/_Compositions/sidebar"&gt;the Sidebar component&lt;/a&gt; as an example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sidebar-l.webc&lt;/code&gt; WebC component file – This is where the component itself is defined, in this case the WebC component is a wrapper for a pre-defined true web component so it only loads the CSS and JS. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sidebar.config.js&lt;/code&gt; Configuration file – Defines the component name and any additional context needed to demonstrate what the component can do.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Sidebar.css&lt;/code&gt; Web Component – it’s not required to store component styles here rather than with the rest of the site styles but can be a nice way to keep everything together in one place.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Sidebar.js&lt;/code&gt; Web Component JavaScript – This is where the Web Component is initialized using standard &lt;code&gt;HTMLElement&lt;/code&gt; and &lt;code&gt;customElements&lt;/code&gt; syntax.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sidebar.njk&lt;/code&gt; style guide example – The Nunjucks template file is where we demonstrate the component with example content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out &lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main/src/_includes/components/card"&gt;the Card component&lt;/a&gt; for an example of a simpler &lt;a href="https://www.11ty.dev/docs/languages/webc/#html-only-components"&gt;HTML-only component&lt;/a&gt;. This one contains a .webc component, a config, and a Nunjucks template for rendering the examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send me your feedback!
&lt;/h2&gt;

&lt;p&gt;Did you add this Eleventy Style Guide Generator to your site? I’d love to hear about it! If you’re willing to share your process adding it as well, that could inform future updates and documentation to make it easier for the next person. So please do &lt;a href="https://stevenwoodson.com/project-inquiry/"&gt;reach out&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>eleventy</category>
      <category>designsystem</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>Pulling WordPress Post Categories &amp; Tags Into Eleventy</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Tue, 15 Aug 2023 12:55:00 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/pulling-wordpress-post-categories-tags-into-eleventy-5bjc</link>
      <guid>https://dev.to/stevenwoodson/pulling-wordpress-post-categories-tags-into-eleventy-5bjc</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F08%2FPulling-WordPress-Post-Categories-Tags-Into-Eleventy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F08%2FPulling-WordPress-Post-Categories-Tags-Into-Eleventy.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;This is another part in a series of posts about WordPress content being pulled into Eleventy, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://stevenwoodson.com/blog/composable-architecture-powered-by-wordpress/" rel="noopener noreferrer"&gt;Composable Architecture Powered by WordPress&lt;/a&gt; (where it all started)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stevenwoodson.com/blog/pulling-wordpress-content-into-eleventy/" rel="noopener noreferrer"&gt;Pulling WordPress Content into Eleventy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stevenwoodson.com/blog/adding-a-table-of-contents-to-dynamic-content-in-11ty/" rel="noopener noreferrer"&gt;Adding a Table of Contents to dynamic content in 11ty&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is specifically expanding on the progress from the &lt;a href="https://stevenwoodson.com/blog/pulling-wordpress-content-into-eleventy/" rel="noopener noreferrer"&gt;Pulling WordPress Content into Eleventy&lt;/a&gt; post to add categories and tags to my blog posts. I’m also going to add per category-filtered pages, and a per tags-filtered pages for good measure.&lt;/p&gt;

&lt;p&gt;Here’s what it’s going to look like when it’s all done, or you can &lt;a href="https://stevenwoodson.com/blog/" rel="noopener noreferrer"&gt;click around my blog&lt;/a&gt; to see it for yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F08%2Flocalhost_8080_blog_-1024x600.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F08%2Flocalhost_8080_blog_-1024x600.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Category Link List&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F08%2Flocalhost_8080_blog_category_webdev_-1024x595.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F08%2Flocalhost_8080_blog_category_webdev_-1024x595.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Posts filtered by Category&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F08%2Flocalhost_8080_blog_eleventy-style-guide-generator-with-webc-component-support_-1024x887.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F08%2Flocalhost_8080_blog_eleventy-style-guide-generator-with-webc-component-support_-1024x887.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Categories and Tags listed at the top of a post&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;As I’ve been focusing more on blogging lately, I’ve amassed enough posts that I started realizing that it’s getting progressively harder to find a blog post the older it gets.&lt;/p&gt;

&lt;p&gt;Really the only way to do so currently is to go to the main blog page and scroll or run a page search. Not ideal. I could add a site search but I’ve been dragging my heels on that too, so I’m opting for something a little easier for now.&lt;/p&gt;

&lt;p&gt;Time to utilize the built in functionality of Categories and Tags from WordPress!&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding Categories and Tags to Posts
&lt;/h2&gt;

&lt;p&gt;First things first, we need to make sure to add some categories and tags to blog posts. I hadn’t up until now because I wasn’t using them, so I spent some time coming up with a list of categories and tags I’d like to use and then using “Quick Edit” to apply them to posts quickly. Check out &lt;a href="https://wordpress.com/learn/courses/intro-to-blogging/categories-and-tags/" rel="noopener noreferrer"&gt;this Categories and tags article&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Once we have that data set up in WordPress, we need to make sure we’re gathering it in Eleventy when performing a build. We need to have a list of &lt;strong&gt;categories&lt;/strong&gt; and &lt;strong&gt;tags&lt;/strong&gt; attributed to the post in order to link to them in the blog post template.&lt;/p&gt;

&lt;p&gt;These are collectively what WordPress refers to as &lt;code&gt;terms&lt;/code&gt;. The details (slug, title, etc.) of terms are not surfaced in the default REST API response, instead we’re only going to see references to their IDs in the main data object like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"categories": [7],
"tags": [34],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll also see RESTful links inside &lt;code&gt;_links&lt;/code&gt; (which would return these details separately) like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"wp:term": [
  {
    "taxonomy": "category",
    "embeddable": true,
    "href": "https://mysite.com/wp-json/wp/v2/categories?post=1"
  },
  {
    "taxonomy": "post_tag",
    "embeddable": true,
    "href": "https://mysite.com/wp-json/wp/v2/tags?post=1"
  }
],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Getting terms in the REST API response
&lt;/h3&gt;

&lt;p&gt;Instead of performing multiple queries to get the category and tag data, we can add them to the embed section of the same query we’re already using.&lt;/p&gt;

&lt;p&gt;As noted in the previous post about pulling content from WordPress, I’m splitting out the posts method &lt;a href="http://localhost:8080/blog/pulling-wordpress-content-into-eleventy/#getallposts" rel="noopener noreferrer"&gt;getAllPosts&lt;/a&gt; from the post details method &lt;a href="https://stevenwoodson.com/blog/pulling-wordpress-content-into-eleventy/#requestposts" rel="noopener noreferrer"&gt;requestPosts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To add the terms details, we add &lt;code&gt;&amp;amp;_embed=wp:term&lt;/code&gt; to the API request in &lt;code&gt;requestPosts&lt;/code&gt;. So in our previous code &lt;code&gt;_embed: "wp:featuredmedia",&lt;/code&gt; turns into &lt;code&gt;_embed: "wp:featuredmedia,wp:term",&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, we need to make sense of that data and add it to the blogpost data object we’re using to generate the blog pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Organizing the term data
&lt;/h3&gt;

&lt;p&gt;WordPress doesn’t discern between a “category” and a “tag” in the embedded JSON, all terms are stored together with an associated &lt;strong&gt;taxonomy&lt;/strong&gt;. Categories are in the &lt;code&gt;category&lt;/code&gt; taxonomy, and tags are in the &lt;code&gt;post_tag&lt;/code&gt; taxonomy.&lt;/p&gt;

&lt;p&gt;For the same example as above where there’s one category whose ID is 7 and one tag with an ID of 34, here’s a slightly trimmed version of what that raw data ends up looking like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"wp:term": [
  [
    {
      "id": 7,
      "link": "https://mysite.com/category/webdev/",
      "name": "Web Dev",
      "slug": "webdev",
      "taxonomy": "category",

    }
  ],
  [
    {
      "id": 34,
      "link": "https://mysite.com/tag/eleventy/",
      "name": "eleventy",
      "slug": "eleventy",
      "taxonomy": "post_tag",
    }
  ]
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we’re going to need to separate categories and tags ourselves to be able to use them in those separate contexts. Here’s how I’m doing it.&lt;/p&gt;

&lt;p&gt;Before&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;metaDescription: metaDescription,
slug: post.slug,
title: post.title.rendered,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;metaDescription: metaDescription,
slug: post.slug,
title: post.title.rendered,
terms: post._embedded["wp:term"] ? post._embedded["wp:term"] : null,
categories: post.categories,
tags: post.tags,
categoriesDetail:
  post._embedded["wp:term"]?.length &amp;gt; 0
    ? post._embedded["wp:term"].filter(
        (term) =&amp;gt; term[0]?.taxonomy == "category"
      )[0]
    : null,
tagsDetail:
  post._embedded["wp:term"]?.length &amp;gt; 0
    ? post._embedded["wp:term"].filter(
        (term) =&amp;gt; term[0]?.taxonomy == "post_tag"
      )[0]
    : null,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s a little gnarly looking, but we don’t want our code to break if there aren’t any categories or tags defined so the checks are all squished in there too. If tags are found, I’m then filtering out categories and tags separately in the returned post data.&lt;/p&gt;

&lt;p&gt;You’ll notice I’m also passing along the array of categories and tags IDs in &lt;code&gt;categories&lt;/code&gt; and &lt;code&gt;tags&lt;/code&gt; too, this is for more easily filtering blog posts by these terms in the individual category and tag pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blog Post Updates
&lt;/h2&gt;

&lt;p&gt;Now that I have post categories and tags, time to add them to the blog post pages!&lt;/p&gt;

&lt;h3&gt;
  
  
  Categories
&lt;/h3&gt;

&lt;p&gt;I have “Published”, “Last Updated”, and reading time already listed at the top of the page just below the headline and above the blog contents. I want to add the category(ies) here too, so here’s the relevant Nunjucks template code for that addition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% if blogpost.categoriesDetail %}
  &amp;lt;p&amp;gt;
    &amp;lt;strong&amp;gt;Posted in &amp;lt;/strong&amp;gt;
    {% for category in blogpost.categoriesDetail %}
      &amp;lt;a href="{{ '/blog/category/' + category.slug | url }}"&amp;gt;{{ category.name }}&amp;lt;/a&amp;gt;
      {% if not loop.last %}, {% endif %}
    {% endfor %}
  &amp;lt;/p&amp;gt;
{% endif %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’m checking for the presence of categories first, some of my posts aren’t categorized yet so I don’t want this to show at all for those. I then loop through the categories defined (because there can be multiple) and render a comma separated list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;The Nunjucks template code for tags is similar to the categories above, I ended up adding it to the top of the page as well but am considering moving to the bottom.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% if blogpost.tagsDetail %}
  &amp;lt;p&amp;gt;
    &amp;lt;span aria-hidden="true" class="fe fe-tag"&amp;gt;&amp;lt;/span&amp;gt;
    &amp;lt;strong&amp;gt;
      {% if blogpost.tagsDetail.length &amp;gt; 1 %}Tags{% else %}Tag{% endif %}
    &amp;lt;/strong&amp;gt;:
  {% for tag in blogpost.tagsDetail %}
      &amp;lt;a href="{{ '/blog/tag/' + tag.slug | url }}"&amp;gt;{{ tag.name }}&amp;lt;/a&amp;gt;
      {% if not loop.last %}, {% endif %}
    {% endfor %}
  &amp;lt;/p&amp;gt;
{% endif %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The biggest difference with this template snippet is that I’m pluralizing “Tags” instead of “Tag” if there are more than one.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Pages
&lt;/h2&gt;

&lt;p&gt;These additions are all looking great, but if you’re following along you may notice that the links I’ve added to the blog post template are all going to 404 pages. Of course, that’s because they don’t exist yet so let’s get on that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gathering Categories and Tags Data
&lt;/h3&gt;

&lt;p&gt;I’ve not figured out a way to compile all the categories and tags into their own collections by using the data we’ve already gathered in blog posts. Instead, I had to run separate REST API calls for them. If you know of a better way to do this please do let me know!&lt;/p&gt;

&lt;p&gt;In setting up these two new REST API calls, I noticed quite a bit of duplication between them, so I opted to do some cleanup and isolate the API calls and data manipulation needed for them all. I called it &lt;code&gt;/utils/wp-json.js&lt;/code&gt;. Here’s the code for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { AssetCache } = require("@11ty/eleventy-fetch");
const axios = require("axios");
const jsdom = require("jsdom");

// Config
const ITEMS_PER_REQUEST = 10;

/**
 * WordPress API call by page
 *
 * @param {Int} page - Page number to fetch, defaults to 1
 * @return {Object} - Total, Pages, and full API data
 */
async function requestPage(apiBase, page = 1) {
  try {
    // https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/
    const url = apiBase;
    const params = {
      params: {
        page: page,
        per_page: ITEMS_PER_REQUEST,
        _embed: "wp:featuredmedia,wp:term",
        order: "desc",
      },
    };
    const response = await axios.get(url, params);

    return {
      total: parseInt(response.headers["x-wp-total"], 10),
      pages: parseInt(response.headers["x-wp-totalpages"], 10),
      data: response.data,
    };
  } catch (err) {
    console.error("API not responding, no data returned", err);
    return {
      total: 0,
      pages: 0,
      data: [],
    };
  }
}

/**
 * Get all data from a WordPress API endpoint
 * Use cached values if available, pull from API if not.
 *
 * @return {Array} - array of data objects
 */
async function getAllContent(API_BASE, ASSET_CACHENAME) {
  const cache = new AssetCache(ASSET_CACHENAME);
  let requests = [];
  let apiData = [];

  if (cache.isCacheValid("2h")) {
    console.log("Using cached " + ASSET_CACHENAME);
    return cache.getCachedValue();
  }

  // make first request and marge results with array
  const request = await requestPage(API_BASE);
  console.log(
    "Using API " +
      ASSET_CACHENAME +
      ", retrieving " +
      request.pages +
      " pages, " +
      request.total +
      " total records."
  );
  apiData.push(...request.data);

  if (request.pages &amp;gt; 1) {
    // create additional requests
    for (let page = 2; page &amp;lt;= request.pages; page++) {
      const request = requestPage(API_BASE, page);
      requests.push(request);
    }

    // resolve all additional requests in parallel
    const allResponses = await Promise.all(requests);
    allResponses.map((response) =&amp;gt; {
      apiData.push(...response.data);
    });
  }

  // return data
  await cache.save(apiData, "json");
  return apiData;
}

/**
 * Clean up and convert the API response for our needs
 */
async function processContent(content) {
  return Promise.all(
    content.map(async (post) =&amp;gt; {
      // remove HTML-Tags from the excerpt for meta description
      let metaDescription = post.excerpt.rendered.replace(/(&amp;lt;([^&amp;gt;]+)&amp;gt;)/gi, "");
      metaDescription = metaDescription.replace("\n", "");

      // Code highlighting with Eleventy Syntax Highlighting
      // https://www.11ty.dev/docs/plugins/syntaxhighlight/
      const formattedContent = highlightCode(prepared.content);

      // Return only the data that is needed for the actual output
      return await {
        content: post.content.rendered,
        formattedContent: formattedContent,
        custom_fields: post.custom_fields ? post.custom_fields : null,
        date: post.date,
        dateRFC3339: new Date(post.date).toISOString(),
        modifiedDate: post.modified,
        modifiedDateRFC3339: new Date(post.modified).toISOString(),
        excerpt: post.excerpt.rendered,
        formattedDate: new Date(post.date).toLocaleDateString("en-US", {
          year: "numeric",
          month: "long",
          day: "numeric",
        }),
        formattedModifiedDate: new Date(post.modified).toLocaleDateString(
          "en-US",
          {
            year: "numeric",
            month: "long",
            day: "numeric",
          }
        ),
        heroImageFull:
          post._embedded["wp:featuredmedia"] &amp;amp;&amp;amp;
          post._embedded["wp:featuredmedia"].length &amp;gt; 0
            ? post._embedded["wp:featuredmedia"][0].media_details.sizes.full
                .source_url
            : null,
        heroImageThumb:
          post._embedded["wp:featuredmedia"] &amp;amp;&amp;amp;
          post._embedded["wp:featuredmedia"].length &amp;gt; 0
            ? post._embedded["wp:featuredmedia"][0].media_details.sizes
                .medium_large
              ? post._embedded["wp:featuredmedia"][0].media_details.sizes
                  .medium_large.source_url
              : post._embedded["wp:featuredmedia"][0].media_details.sizes.full
                  .source_url
            : null,
        metaDescription: metaDescription,
        slug: post.slug,
        title: post.title.rendered,
        terms: post._embedded["wp:term"] ? post._embedded["wp:term"] : null,
        categories: post.categories,
        tags: post.tags,
        categoriesDetail:
          post._embedded["wp:term"]?.length &amp;gt; 0
            ? post._embedded["wp:term"].filter(
                (term) =&amp;gt; term[0]?.taxonomy == "category"
              )[0]
            : null,
        tagsDetail:
          post._embedded["wp:term"]?.length &amp;gt; 0
            ? post._embedded["wp:term"].filter(
                (term) =&amp;gt; term[0]?.taxonomy == "post_tag"
              )[0]
            : null,
      };
    })
  );
}

function sortNameAlpha(content) {
  return content.sort((a, b) =&amp;gt; {
    if (a.name &amp;lt; b.name) return -1;
    else if (a.name &amp;gt; b.name) return 1;
    else return 0;
  });
}

module.exports = {
  requestPage: requestPage,
  getAllContent: getAllContent,
  processContent: processContent,
  sortNameAlpha: sortNameAlpha,
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this new set of utilities, the actual data JS files are super small. Here are the &lt;code&gt;blogposts.js&lt;/code&gt;, &lt;code&gt;blogcategories.js&lt;/code&gt;. and &lt;code&gt;blogtags.js&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;blogposts.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { getAllContent, processContent } = require("../utils/wp-json");

const API_BASE =
  "https://mysite.com/wp-json/wp/v2/posts";
const ASSET_CACHENAME = "blogposts";

// export for 11ty
module.exports = async () =&amp;gt; {
  const blogposts = await getAllContent(API_BASE, ASSET_CACHENAME);
  const processedPosts = await processContent(blogposts);
  return processedPosts;
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;blogcategories.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { getAllContent, sortNameAlpha } = require("../utils/wp-json");

const API_BASE =
  "https://mysite.com/wp-json/wp/v2/categories";
const ASSET_CACHENAME = "blogcategories";

// export for 11ty
module.exports = async () =&amp;gt; {
  const blogcategories = await getAllContent(API_BASE, ASSET_CACHENAME);
  return sortNameAlpha(blogcategories);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;blogtags&lt;/code&gt;.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { getAllContent, sortNameAlpha } = require("../utils/wp-json");

const API_BASE =
  "https://mysite.com/wp-json/wp/v2/tags";
const ASSET_CACHENAME = "blogtags";

// export for 11ty
module.exports = async () =&amp;gt; {
  const blogtags = await getAllContent(API_BASE, ASSET_CACHENAME);
  return sortNameAlpha(blogtags);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a Blog Post Term Filter
&lt;/h3&gt;

&lt;p&gt;Great, we have data now! The next step is to set up a filter so we can show category and tag pages with just the posts that contain them. Because both are ID based, I opted to create one filter that’d work for either. Here’s the code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // Get the elements of a collection that contains the provided ID for the provided taxonomy
  eleventyConfig.addFilter("blogTermFilter", (items, taxonomy, termID) =&amp;gt; {
    return items.filter((post) =&amp;gt; {
      return post[taxonomy].includes(termID);
    });
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I can pass the taxonomy (&lt;code&gt;categories&lt;/code&gt; or &lt;code&gt;tags&lt;/code&gt;) and its ID to get a filtered list of posts with that category or tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Category and Tag Pages
&lt;/h3&gt;

&lt;p&gt;We’re getting really close now!&lt;/p&gt;

&lt;p&gt;The final step is to create the tags and categories filtered pages. For me, they both have basically the same structure so I’m just going to share the categories one here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
layout: layouts/base.njk
pagination:
  data: blogcategories
  size: 1
  alias: blogcategory
permalink: blog/category/{{ blogcategory.slug }}/
---
{% set blogslist = blogposts | blogTermFilter("categories", blogcategory.id) %}

&amp;lt;section class="l-container h-feed hfeed"&amp;gt;
  &amp;lt;header class="feature-list__header"&amp;gt;
    &amp;lt;h1 class="p-name"&amp;gt;Blog Posts categorized under "{{ blogcategory.name }}"&amp;lt;/h1&amp;gt;
    &amp;lt;a href="{{ metadata.feed.path }}"&amp;gt;RSS Feed&amp;lt;/a&amp;gt;
  &amp;lt;/header&amp;gt;
  &amp;lt;p&amp;gt;
    &amp;lt;a href="{{ '/blog/' | url }}" class="btn btn--secondary btn--small"&amp;gt;back to all blog posts&amp;lt;/a&amp;gt;
  &amp;lt;/p&amp;gt;

  &amp;lt;div class="grid-3-wide feature-list"&amp;gt;
    {% for post in blogslist %}
      &amp;lt;div class="card z-depth-1 h-entry hentry"&amp;gt;
        &amp;lt;div class="img-16-9-aspect"&amp;gt;
          {%- if post.heroImageThumb %}
            &amp;lt;img src="{{ post.heroImageThumb }}" alt="" loading="lazy"&amp;gt;
          {% else %}
            &amp;lt;img src="/assets/images/posts/post-hero-placeholder.png" alt="" loading="lazy"&amp;gt;
          {% endif %}
        &amp;lt;/div&amp;gt;

        &amp;lt;div class="card__content"&amp;gt;
          &amp;lt;{{itemheader}} class="headline4 p-name entry-title"&amp;gt;
            &amp;lt;a href="/blog/{{post.slug}}" class="u-url" rel="bookmark"&amp;gt;
              {% if post.title %}{{ post.title | safe }}
              {% endif %}
            &amp;lt;/a&amp;gt;
          &amp;lt;/{{itemheader}}&amp;gt;
          &amp;lt;div class="l-post__meta"&amp;gt;
            &amp;lt;p&amp;gt;
              &amp;lt;strong&amp;gt;
                &amp;lt;span aria-hidden="true" class="fe fe-calendar"&amp;gt;&amp;lt;/span&amp;gt;
                &amp;lt;time class="postlist-date" datetime="{{ post.date }}"&amp;gt;{{ post.formattedDate }}&amp;lt;/time&amp;gt;
              &amp;lt;/strong&amp;gt;
            &amp;lt;/p&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div class="p-summary entry-summary"&amp;gt;
            {%- if post.excerpt %}{{ post.excerpt | safe }}
            {% endif %}
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    {% endfor %}
  &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can copy this file and replace the references to categories over to tags instead for the Tags version. For example &lt;code&gt;{% set blogslist = blogposts | blogTermFilter("tags",blogtag.id) %}&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Potential Further Enhancements
&lt;/h2&gt;

&lt;p&gt;This ended up being a bit more to figure out than I anticipated going into it, and I’m pretty happy with where it is now.&lt;/p&gt;

&lt;p&gt;I do, however, have some ideas for future further enhancements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In addition to Previous and Next posts at the bottom of a blog post page, it’d be really great to have &lt;strong&gt;a “Related posts” section&lt;/strong&gt;. That’s a fairly common feature of blogs and would help with discoverability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced filtering&lt;/strong&gt; from within the main Blog page would be nice, rather than just separate pages per category and tag. This would open up further options like filtering by category and tag together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I may make time for these soon, let me know if you’d be interested in reading more about that!&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>api</category>
      <category>eleventy</category>
    </item>
    <item>
      <title>Eleventy Style Guide Generator with WebC Component Support</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Fri, 04 Aug 2023 10:20:17 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/eleventy-style-guide-generator-with-webc-component-support-1e4j</link>
      <guid>https://dev.to/stevenwoodson/eleventy-style-guide-generator-with-webc-component-support-1e4j</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--neDpbVMD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/08/Eleventy-Style-Guide-Generator-with-WebC-Components.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--neDpbVMD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/08/Eleventy-Style-Guide-Generator-with-WebC-Components.png" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I created an Eleventy style guide generator, features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatically itemized JSON-formatted &lt;strong&gt;design tokens&lt;/strong&gt; like colors, fonts, and fluid scale and sizing.&lt;/li&gt;
&lt;li&gt;Automatically itemized &lt;strong&gt;components and their variations&lt;/strong&gt; based on a simple configuration file format&lt;/li&gt;
&lt;li&gt;Eleventy &lt;strong&gt;&lt;a href="https://www.11ty.dev/docs/languages/webc/"&gt;WebC Component&lt;/a&gt; support&lt;/strong&gt; and examples&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;standalone documentation pages&lt;/strong&gt; , perfect for displaying foundational HTML elements and explaining design system details in a more curated way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/stevenwoodson/11ty-design-system"&gt;Source is on GitHub&lt;/a&gt; and &lt;a href="https://stevenwoodson.github.io/11ty-design-system/"&gt;here’s the demo site&lt;/a&gt; it generates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impetus
&lt;/h2&gt;

&lt;p&gt;I recently had the opportunity to create a new fairly simple Eleventy site and found myself wanting to also document some of the visual language and components therein.&lt;/p&gt;

&lt;p&gt;I didn’t want to go overboard and install something like Storybook for this because – while I love Storybook – it can be &lt;em&gt;a lot&lt;/em&gt; and this site definitely didn’t need all that.&lt;/p&gt;

&lt;p&gt;So, in true developer fashion, rather than manually create a few static style guide pages and move on, I decided to make a design system generator!&lt;/p&gt;

&lt;p&gt;I love the &lt;strong&gt;simplicity and speed of 11ty&lt;/strong&gt; , and I love the organization of a good &lt;strong&gt;self documenting design system&lt;/strong&gt; , so I combined the excellent work of several really smart people to pull these two together in a way that acts as a pretty nice starting point for a fluid, responsive, and blazing fast 11ty powered website.&lt;/p&gt;

&lt;p&gt;Read on if you’re interested in the pieces that make up the whole, including the inspiration behind them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Tokens
&lt;/h2&gt;

&lt;p&gt;What better place to start than the at the smallest building blocks of a design system, right?&lt;/p&gt;

&lt;p&gt;Need a quick intro to design tokens and how they relate to design systems? &lt;a href="https://www.smashingmagazine.com/2019/11/smashing-podcast-episode-3/" rel="nofollow"&gt;Check out this “What Are Design Tokens?” Smashing Magazine podcast episode with Jina Anne&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Design tokens are vital to the overall look and feel and are (ideally) used everywhere to promote consistency with spacing, fonts, colors, etc.&lt;/p&gt;

&lt;p&gt;All tokens are stored in the &lt;code&gt;/src/_data/designTokens/&lt;/code&gt; directory, I did this purposefully because having the JSON in the Eleventy Data folder gives me these values to manipulate and use for free. Making the visual itemizing noted below really simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Token Definition
&lt;/h3&gt;

&lt;p&gt;Here’s the structure I used for the token files themselves, they all came with a title, description, and an array of items. I also optionally added a meta object to store additional info, for example the sizes and spacing ones link to the Utopia calculator where it was generated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "title": "Name for these Tokens",
  "description": "Longform description of what these tokens define.",
  "meta": {},
  "items": []
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used the excellent tools James and Trys created called &lt;a href="https://utopia.fyi/"&gt;Utopia&lt;/a&gt; for the sizes and spacing tokens. Here’s the breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://utopia.fyi/type/calculator"&gt;Type Calculator&lt;/a&gt; defines the &lt;a href="https://github.com/stevenwoodson/11ty-design-system/blob/main/src/_data/designTokens/sizes.json"&gt;sizes.json&lt;/a&gt; tokens&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://utopia.fyi/space/calculator"&gt;Space Calculator&lt;/a&gt; defines the &lt;a href="https://github.com/stevenwoodson/11ty-design-system/blob/main/src/_data/designTokens/spacing.json"&gt;spacing.json&lt;/a&gt; tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also added a &lt;a href="https://github.com/stevenwoodson/11ty-design-system/blob/main/src/_data/designTokens/colors.json"&gt;colors.json&lt;/a&gt; and &lt;a href="https://github.com/stevenwoodson/11ty-design-system/blob/main/src/_data/designTokens/fonts.json"&gt;fonts.json&lt;/a&gt; token JSON file to itemize those for consistency as well.&lt;/p&gt;

&lt;p&gt;I now have a solid foundation for fluid typography and spacing, and consistent fonts and colors, so the applications I build will look wonderful regardless of the available space and will be consistent from page to page with minimal effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Converting to CSS Variables
&lt;/h3&gt;

&lt;p&gt;Now that I have my tokens beautifully organized, I also needed a way to convert them into usable CSS variables.&lt;/p&gt;

&lt;p&gt;There are some great well established tools out there that do this, such as &lt;a href="https://github.com/salesforce-ux/theo"&gt;Theo&lt;/a&gt; and &lt;a href="https://amzn.github.io/style-dictionary/#/"&gt;Style Dictionary&lt;/a&gt; but for this I turned to the sage advice of Andy Bell via &lt;a href="https://buildexcellentwebsit.es/"&gt;buildexcellentwebsit.es&lt;/a&gt; and used Tailwind to run through the token JSON files and generate the CSS variables I can use in my styling.&lt;/p&gt;

&lt;p&gt;I went this route because it seemed to fit how I like to organize things, already had concepts inherent to Utopia (including &lt;code&gt;clamp&lt;/code&gt;), and was already working in an Eleventy site.&lt;/p&gt;

&lt;p&gt;These are defined in the &lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main/src/_utilities/css-utils"&gt;/_utilities/css-utils&lt;/a&gt; folder and used in the &lt;a href="https://github.com/stevenwoodson/11ty-design-system/blob/main/tailwind.config.js"&gt;tailwind.config.js&lt;/a&gt; configuration.&lt;/p&gt;

&lt;p&gt;Here’s a truncated example of what it generates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:root {
    --color-bg: #fff;
    --color-bg-alt: #fffaeb;
    ...
    --space-2xs: clamp(0.5rem,0.46rem + 0.19vw,0.625rem);
    --space-xs: clamp(0.75rem,0.69rem + 0.29vw,0.9375rem);
    ...
    --space-xs-s: clamp(0.75rem,0.59rem + 0.78vw,1.25rem);
    --space-s-m: clamp(1rem,0.53rem + 2.33vw,2.5rem);
    ...
    --size-step-n2: clamp(0.6875rem,0.65rem + 0.19vw,0.8125rem);
    ...
    --font-base: Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;
    --font-serif: Georgia,sans-serif;
    --font-code: Lucida Console,Monaco,monospace;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Visually Itemizing the Tokens
&lt;/h3&gt;

&lt;p&gt;Being so vitally important to a successful product, I wanted a seamless and automated way to itemize these tokens for easy visual reference.&lt;/p&gt;

&lt;p&gt;I’m still shocked at how easy this part ended up being once I decided to store the design tokens in the Eleventy data directory.&lt;/p&gt;

&lt;p&gt;Because that data was automatically surfaced to page templates, all I needed to do here was to create an 11ty Nunjucks-based page that loops through the design token values and applies the tokens to some placeholder elements. You can see how this comes together at &lt;a href="https://github.com/stevenwoodson/11ty-design-system/blob/main/src/design-system/Atoms/tokens.njk"&gt;/src/design-system/Atoms/tokens.njk&lt;/a&gt; and a screenshot of it in action below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stevenwoodson.github.io/11ty-design-system/design-system/Atoms/tokens/"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fmcRbuPS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/08/Screen-Shot-2023-08-03-at-11.47.09-AM-1024x456.png" alt="Three screenshots of the same page horizontally overlapping to illustrate some of the page contents more easily. You can go to this page using the link on this image." width="800" height="356"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Some overlapping screenshots of the tokens style guide page to illustrate how it itemizes the tokens visually&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Style Guide Generator
&lt;/h2&gt;

&lt;p&gt;With design tokens defined, accessible via CSS, and visually itemized in a page. I thought “wouldn’t it be great to have individual components automatically itemized in a similar way?”.&lt;/p&gt;

&lt;h3&gt;
  
  
  Foundation
&lt;/h3&gt;

&lt;p&gt;After some searching for existing solutions I found &lt;a href="https://github.com/trys/eleventy-design-system/"&gt;this Eleventy Design System gem&lt;/a&gt; by Trys Mudford. It’s truly a wonderful foundation for exactly what I was hoping to achieve with this project.&lt;/p&gt;

&lt;p&gt;While integrating that into what I was building, I noticed a few things that I wanted to expand upon to make it more versatile. Here’s the highlights of what I added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Moved to a subfolder so it can be in the same codebase as the rest of the site&lt;/li&gt;
&lt;li&gt;Ability to define components in subfolders of the &lt;code&gt;_includes/components&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;WebC components support and examples&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  WebC Support
&lt;/h3&gt;

&lt;p&gt;It just so happened that I was reading more about &lt;a href="https://www.11ty.dev/docs/languages/webc/"&gt;WebC&lt;/a&gt; while iterating on this idea. WebC is a new 11ty file extension that brings first-class components to Eleventy. An obvious addition to a style guide generator that itemizes components!&lt;/p&gt;

&lt;p&gt;I will caution that WebC is still fairly new, there’s some things that work great with other file extensions that just don’t yet for WebC. I’ve itemized some of these &lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main#todo"&gt;as TODO items in the readme&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To see some WebC component examples, check out the &lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main/src/_includes/components/_Compositions/sidebar"&gt;Sidebar&lt;/a&gt; and &lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main/src/_includes/components/_Compositions/stack"&gt;Stack&lt;/a&gt; composition components courtesy of &lt;a href="https://every-layout.dev/"&gt;Every Layout&lt;/a&gt;, and the &lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main/src/_includes/components/card"&gt;Card&lt;/a&gt; component utilizing some style inspiration from &lt;a href="https://smolcss.dev/"&gt;SmolCSS&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Conventions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Standalone Documentation Pages
&lt;/h3&gt;

&lt;p&gt;Rather than having to create a “component” for foundational HTML elements and long form documentation within the style guide, I’ve built it so that you can define standard pages in the style guide as you would any other page.&lt;/p&gt;

&lt;p&gt;It will automatically merge these static pages with the generated ones for components too!&lt;/p&gt;

&lt;p&gt;For some examples of static pages, check out the &lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main/src/design-system/Atoms"&gt;src/design-system/Atoms&lt;/a&gt; folder. That’s where I’ve added block, form, inline, and preformatted HTML tags to quickly be able to preview how changes to the design tokens and styles affect these critical native tags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Styling Methodology
&lt;/h3&gt;

&lt;p&gt;I’ve quickly become a big fan of &lt;a href="https://cube.fyi/"&gt;CubeCSS&lt;/a&gt; and have tried to adhere to those guidelines as best I can here. If you haven’t read about it yet I highly recommend you check that site out and give it a try.&lt;/p&gt;

&lt;p&gt;Admittedly, in some cases it gets a little weird but I have my reasons! For example, I have web component styles sitting in the same folder as the web component itself (&lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main/src/_includes/components/_Compositions/sidebar"&gt;Sidebar&lt;/a&gt; for example) and I have others (&lt;a href="https://github.com/stevenwoodson/11ty-design-system/tree/main/src/_includes/components/card"&gt;Card&lt;/a&gt; for example) that have styles in the assets folder (&lt;a href="https://github.com/stevenwoodson/11ty-design-system/blob/main/src/assets/css/blocks/card.css"&gt;card.css&lt;/a&gt; in this case) instead.&lt;/p&gt;

&lt;p&gt;I did this because I wanted to adhere to the &lt;a href="https://www.11ty.dev/docs/languages/webc/#html-only-components"&gt;HTML-only Components&lt;/a&gt; rule where it renders native HTML and ignores the host component tag. Adding and referencing the styles in the component definition would have turned the Card example into a true web component instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Site Example
&lt;/h2&gt;

&lt;p&gt;I’ve actually been sitting on this for a little while, before releasing something for general use I wanted to make sure to put it through a real site build process first. Happy to share the first such site has just been pushed live for the best (and most patient) client a guy could ask for – my dad!&lt;/p&gt;

&lt;p&gt;His site at &lt;a href="https://dannywoodson.org/"&gt;dannywoodson.org&lt;/a&gt; and &lt;a href="https://dannywoodson.org/design-system/"&gt;the style guide for it is here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pretty neat to be able to compare the demo site with this live site and see the similarities, but also the differences!&lt;/p&gt;

&lt;h2&gt;
  
  
  Plans for the Future
&lt;/h2&gt;

&lt;p&gt;I think there’s a lot of potential here, a couple of the bigger things I’d love to do with this are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Continue to add foundational web components that can be reused across any kind of tech stack. Im considering adding some more baseline WebC components to this project as well, especially those found in &lt;a href="https://github.com/11ty/tugboat/tree/main/_components"&gt;Tugboat&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I’d ideally like this to be an Eleventy plugin that can be installed on an Eleventy site, rather than a full Eleventy site in itself. As far as I can tell there’s not a way to do so until &lt;a href="https://github.com/11ty/eleventy/issues/1612"&gt;Virtual Templates&lt;/a&gt; are made available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have any ideas? Are you going to give it a try?&lt;/p&gt;

&lt;p&gt;I’d love to hear from you, reach out on the socials or start up &lt;a href="https://github.com/stevenwoodson/11ty-design-system/issues/new"&gt;an issue on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Design Systems, Fluid Typography, Styling Methodologies oh my!
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fluid type, space, and grid calculations via &lt;a href="https://utopia.fyi/"&gt;Utopia&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CSS methodology based on &lt;a href="https://cube.fyi/"&gt;Cube CSS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/trys/eleventy-design-system/"&gt;Eleventy Design System&lt;/a&gt; by Trys Mudford &amp;amp; the companion explanatory &lt;a href="https://www.trysmudford.com/blog/eleventy-design-system/"&gt;blog post&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Some of the base components and styles are based on &lt;a href="https://smolcss.dev/"&gt;SmolCSS&lt;/a&gt; and &lt;a href="https://every-layout.dev/"&gt;Every Layout&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Getting Started with WebC
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Eleventy &lt;a href="https://www.11ty.dev/docs/languages/webc/"&gt;WebC Components&lt;/a&gt; main documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=X-Bpjrkz-V8"&gt;Crash Course in Eleventy’s new WebC Plugin&lt;/a&gt; video&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=p0wDUK0Z5Nw"&gt;Interactive Progressively-enhanced Web Components with WebC&lt;/a&gt; video&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://11ty.rocks/posts/introduction-webc/"&gt;Introduction to WebC&lt;/a&gt; &amp;amp; &lt;a href="https://11ty.rocks/posts/understanding-webc-features-and-concepts/"&gt;Understanding WebC Features and Concepts&lt;/a&gt; from 11ty Rocks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://11ty.webc.fun/"&gt;11ty &amp;amp; WebC&lt;/a&gt; by W. Evan Sheehan&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>eleventy</category>
      <category>designsystem</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>The Ideal Client and Project – Freelance Introspection</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Sat, 08 Jul 2023 14:19:25 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/the-ideal-client-and-project-freelance-introspection-36dj</link>
      <guid>https://dev.to/stevenwoodson/the-ideal-client-and-project-freelance-introspection-36dj</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KANtigOC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/07/The-Ideal-Client-and-Project-Freelance-Introspection.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KANtigOC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/07/The-Ideal-Client-and-Project-Freelance-Introspection.png" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve realized there’s a surprising amount of introspection required when you decide to go freelance. One is to figure out “the ideal” client and project.&lt;/p&gt;

&lt;p&gt;When working at a company we don’t get the luxury of considering these ideals, you’re assigned a project and that’s that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Now that I’m actively looking for opportunities, it’s important to have those ideals in mind so I can try to target those companies and projects that would be the best fit for me.&lt;/p&gt;

&lt;p&gt;I’m gonna be honest, I still don’t have a great answer to this yet. Since I committed to sharing more of my process of going freelance, I’m posting my process now and hope to have an update soon with a clearer answer.&lt;/p&gt;

&lt;p&gt;Here’s some questions I’m mulling over so far, I feel like the answers of these will help me get there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are some recent projects that made me feel the most proud when it was completed?&lt;/li&gt;
&lt;li&gt;What kind of project teams did I tend to gel with the best?&lt;/li&gt;
&lt;li&gt;What technologies were in use that made me the most fulfilled as a developer?&lt;/li&gt;
&lt;li&gt;What kinds of clients inspired me to give my all every day?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Taking it Social
&lt;/h2&gt;

&lt;p&gt;An earlier draft of this went out on &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7082439318253813761/"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://twitter.com/stevenwoodson/status/1676674141242511388"&gt;Twitter&lt;/a&gt;, and &lt;a href="https://mastodon.online/@stevenwoodson/110663116338677052"&gt;Mastodon&lt;/a&gt;. I got a couple of very interesting replies that I’m going to add to my own process, sharing them below so you can give them a go as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alistair Shepherd
&lt;/h3&gt;

&lt;p&gt;Unrelated, but I gotta &lt;a href="https://alistairshepherd.uk/"&gt;highlight the creativity of Alistair’s website&lt;/a&gt;, especially the header. If you’re interested in the process that led to that amazing result check out &lt;a href="https://2022.stateofthebrowser.com/speaker/alistair-shepherd/"&gt;his talk from the 2022 State of the Browser conference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Many thanks to Alistair for sharing this gem via Mastodon.&lt;/p&gt;

&lt;p&gt;It really makes sense to focus on the people and industries that resonate and provide more satisfaction. Something I’ll be keeping in mind in the work ahead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jon Gibbins
&lt;/h3&gt;

&lt;p&gt;This advice from Jon really highlights his commitment to making the digital world better for all people and for our planet with &lt;a href="https://digitalasitshouldbe.com/"&gt;As It Should Be&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ve certainly found the focus that comes from such introspection to be beneficial to also focussing my marketing efforts and value proposition.&lt;/p&gt;

&lt;p&gt;One exercise I did a few years back was to put a line down the middle of a piece of paper, then write the things / values that matter most to me personally on one side, and the things I think matter most in my work or even the main kinds of projects / clients I’ve worked on / with. Then draw lines from one side to another to connect items that are linked to find common ground. These are areas of focus. Anything else should show where your work is not meeting your personal values.&lt;/p&gt;

&lt;p&gt;You can do something similar with Ikigai exercises.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;Jon Gibbins &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7082439318253813761?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7082439318253813761%2C7082446474705293313%29" rel="nofollow"&gt;via LinkedIn comment&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I really like this mentality of starting with my own values and letting that guide the process of finding common ground for clients and projects.&lt;/p&gt;

&lt;p&gt;I had recently shared my &lt;a href="https://stevenwoodson.com/mission-statement/"&gt;personal and professional mission statement&lt;/a&gt; that I’ve been iterating on for over 5 years, I’m going to try this suggestion with that as my starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  How About You?
&lt;/h2&gt;

&lt;p&gt;Have you done an exercise like this before? If you have some ideas to help me bring this more into focus I’d love to hear it!&lt;/p&gt;

&lt;p&gt;By the way, I’m still looking for freelance web dev opportunities so if you have something coming up I’d love to hear more about &lt;a href="https://stevenwoodson.com/project-inquiry/"&gt;how I can help bring it to life&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>freelance</category>
      <category>process</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Steve Woodson – Independent Web Development Consultant</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Sat, 01 Jul 2023 11:14:57 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/steve-woodson-independent-web-development-consultant-2fdd</link>
      <guid>https://dev.to/stevenwoodson/steve-woodson-independent-web-development-consultant-2fdd</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--58CHClgP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/06/Steve-Woodson-Independent-Web-Development-Consultant.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--58CHClgP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/06/Steve-Woodson-Independent-Web-Development-Consultant.png" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I’m terrible at burying the lede (as it’s the title of this post) so I’m starting with the TL;DR. I’m venturing out on my own offering freelance web development consulting!&lt;/p&gt;

&lt;p&gt;Check out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://stevenwoodson.com/services/"&gt;Services&lt;/a&gt; for my core values, how I’m different, and what web development services I can offer.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stevenwoodson.com/testimonials/"&gt;Testimonials&lt;/a&gt; for some kind words folks have recently shared about me, my work, and my leadership.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stevenwoodson.com/project-inquiry/"&gt;Project Inquiry&lt;/a&gt; to get in contact about an upcoming project I can help you bring to life!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of this post shares some reflections on the past week, and goes into more detail about how I got to the point where I’m ready to go independent, and what else I have planned for the near future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflecting on the Last Week
&lt;/h2&gt;

&lt;p&gt;One week ago today I &lt;a href="https://stevenwoodson.com/blog/i-was-laid-off-now-what/"&gt;shared that I was laid off&lt;/a&gt;. I didn’t realize at the time how much this would resonate with people and am deeply humbled by the response.&lt;/p&gt;

&lt;p&gt;Hundreds of reactions, dozens of comments, and just as many direct messages and emails. I’ve even got a few meetings scheduled with folks just to chat about our experiences and talk about what gets us excited about the future.&lt;/p&gt;

&lt;p&gt;The offer made last week about being open to chat still stands. If you were recently affected by workforce reductions, are struggling to find the next adventure, or just want to chat about the difficulties we’ve all been facing these past few years.&lt;/p&gt;

&lt;p&gt;Feel free to DM me on a social platform or &lt;a href="//mailto:me@stevenwoodson.com"&gt;send me an email&lt;/a&gt;. We can even book a half hour chat if you prefer to talk face to face. Let’s make some lemonade from these lemons!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dream
&lt;/h2&gt;

&lt;p&gt;A few years into my web development career, somewhere around 2005, I started to dream about venturing out on my own. I would even go so far as to take on the odd freelance job when the opportunity would arise.&lt;/p&gt;

&lt;p&gt;But I was a long way from taking a chance on full time freelance web development consulting.&lt;/p&gt;

&lt;p&gt;I knew there was so much I could accomplish but also that the work itself is only one (albeit very important) part. I recognized that there’s marketing, sales, and business management that goes into it as well. Frankly, I was terrified of all three.&lt;/p&gt;

&lt;p&gt;The most visceral reaction, however, was the scarcity mindset. The fear that I’d get everything set up, put myself out there, and nothing happened. I wouldn’t make any digital marketing connections, wouldn’t find clients that saw my potential as a web application developer, wouldn’t land any web dev projects.&lt;/p&gt;

&lt;p&gt;It was enough to deter me from even giving it a try. So, for over 15 years this dream sat there in the back of my mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Now?
&lt;/h2&gt;

&lt;p&gt;It’s amazing how much time one has to sit and think when not engrossed in day to day work. I spent a lot of that time this past week thinking about&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what I’ve yet to accomplish&lt;/li&gt;
&lt;li&gt;what I want my days to look like&lt;/li&gt;
&lt;li&gt;and what is going to get me excited to get up out of bed every morning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I knew right away, there’s still that one big goal that checks all three of these boxes. Maybe losing my job is the universe telling me to just go for it already?&lt;/p&gt;

&lt;p&gt;I had shared that thought privately with several colleagues, friends, and family and every single one had something positive to say. Here are some of them that really resonated with me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“What you water will grow”&lt;/li&gt;
&lt;li&gt;“Sometimes many of us need that kick in the ass.”&lt;/li&gt;
&lt;li&gt;“This is definitely a sign from the universe for the push you needed […] everything happens for a reason!”&lt;/li&gt;
&lt;li&gt;“That would be so great! One door closes, another opens”&lt;/li&gt;
&lt;li&gt;“That’s great to hear on independent consulting. I’m big on universal signals, too!”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve grown a lot in the last handful of years, not only as a developer but as a more confident leader, empathetic collaborator, and accessibility focused technologist. I’ve worked hard building a personal brand and now I feel ready to share all that experience directly with client teams as an independent consultant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it a Reality
&lt;/h2&gt;

&lt;p&gt;I’ve spent a considerable amount of time working through details and getting everything set up. In addition to the site updates noted at the beginning of this post I’ve also secured&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a registered LLC, in partnership with my wife&lt;/li&gt;
&lt;li&gt;business insurance, including cyber coverage&lt;/li&gt;
&lt;li&gt;operations details including communications workfows, project management, and contract templates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m sure there’s countless more things that I need to do, but I’ve got the fundamental aspects ready and am officially &lt;strong&gt;open for business&lt;/strong&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Thought Leadership
&lt;/h3&gt;

&lt;p&gt;Expect to see more “thought leadership” from me, even though I am not altogether fond of that term it does serve as a useful shortcut to what I mean. I have a dozen or so blog posts in draft ranging from technology leadership, productivity, and specific solutions to some of the day to day struggles of web developers.&lt;/p&gt;

&lt;p&gt;I’m still learning when it comes to marketing, but one thing that always resonated with me is the simple fact that sharing what I’ve learned and helping others builds awareness and connections. That awareness and those connections can often lead to surprising and unexpected things.&lt;/p&gt;

&lt;h3&gt;
  
  
  Be Inclusive
&lt;/h3&gt;

&lt;p&gt;I’m also working on more roadmap items for my &lt;a href="https://beinclusive.app/"&gt;SaaS accessibility auditing app Be Inclusive&lt;/a&gt; too. I’ve often lamented not having as much time as I would have liked to keep breathing more life into that app, make it faster, more compelling, and easier to use for the people that work so hard every day to push accessibility forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Got ideas?
&lt;/h3&gt;

&lt;p&gt;What else would you like to see more of from me? If you’ve got some thoughts and ideas I’m certainly interested in hearing more about it.&lt;/p&gt;

&lt;p&gt;I have some time now, and I’m so excited to be able to start spending more of it contributing to the web development, application development, and digital accessibility communities that have helped me learn and grow so much these past 20 years.&lt;/p&gt;

&lt;p&gt;If you have a project coming up that could use an experienced web developer to help bring it to life, &lt;a href="https://stevenwoodson.com/project-inquiry/"&gt;get in touch&lt;/a&gt;! I’d love to hear more about what you have planned, and how I can help.&lt;/p&gt;

&lt;p&gt;Onward and upward! 🎉&lt;/p&gt;

</description>
      <category>consulting</category>
      <category>freelance</category>
    </item>
    <item>
      <title>I Was Laid Off, Now What?</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Wed, 28 Jun 2023 12:33:37 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/i-was-laid-off-now-what-1kk8</link>
      <guid>https://dev.to/stevenwoodson/i-was-laid-off-now-what-1kk8</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qZlU1ZEn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/06/I-Was-Laid-Off-Now-What.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qZlU1ZEn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/06/I-Was-Laid-Off-Now-What.png" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’ve been following the news lately, it’s been a rather tough time in technology this year. Not a week goes by without news of several major companies letting go of significant percentages of their workforce, so much so that many organizations are &lt;a href="https://techcrunch.com/2023/06/05/tech-industry-layoffs-2023/"&gt;keeping track of 2023 tech layoffs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Well, as of last week I’m now one of the tens of thousands affected by these ongoing tech workforce reductions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Facing The Feelings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Grief, Stress, Guilt
&lt;/h3&gt;

&lt;p&gt;A few months ago, because of how pervasive layoffs have been industry-wide lately – including multiple rounds at my previous employer – I had done some reading on how to best support those affected. &lt;a href="https://www.verywellmind.com/job-loss-grief-symptoms-coping-5220039"&gt;This article about Job Loss Grief&lt;/a&gt; and &lt;a href="https://www.helpguide.org/articles/stress/job-loss-and-unemployment-stress.htm"&gt;this one about Unemployment Stress&lt;/a&gt; struck a chord with me and I appreciate their advice even more as I read them again now. I highly recommend them, for folks recently laid off as well as for those interested in helping them.&lt;/p&gt;

&lt;p&gt;Some of the most common advice in these and many other articles was to write about your feelings, share your story, and talk about it. That was the impetus for this post.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Immediate Reactions
&lt;/h3&gt;

&lt;p&gt;My first thoughts mid-call as I realized what was happening were “what about my coachees? what about my project team?” These people are going to be affected by this as well, now needing to carry on with less help and likely more anxiety about their own futures.&lt;/p&gt;

&lt;p&gt;I’ve come to realize how much of a daily routine and sense of self gets tied up in a job after nearly 8 years. The more difficult realization is how quickly it can all disappear. Get the call and minutes later you’re locked out. I understand the precaution of it, and I know it’s never easy for those on the other side too, but there’s an abrupt coldness to it that still stings.&lt;/p&gt;

&lt;p&gt;As strange as it may sound, I also felt a bit of guilt. Thinking of the tasks I postponed til later. That client project ticket I was actively working on. I should have committed that code, I should have written out some notes about my thought process, I should have been more diligent about taking notes about future plans.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reflections
&lt;/h3&gt;

&lt;p&gt;I had jotted down some reflections as they came to mind, here’s some of them.&lt;/p&gt;

&lt;p&gt;Several times I’ve found myself in the Slack app on my phone, my autopilot taking me there to check out the latest messages only to be reminded that it’s gone.&lt;/p&gt;

&lt;p&gt;I was blown away by the dozen or so folks who reached out to me immediately in these first days, that kind of support is not something I expected nor take for granted. I appreciate each and every one of them.&lt;/p&gt;

&lt;p&gt;I have grown so much as a developer, a mentor, and a person in these past 8 years. I had the privilege of being a career coach to 10 wonderful people, worked on dozens of client projects, and had a small part in bringing accessibility to the forefront company-wide.&lt;/p&gt;

&lt;p&gt;I’m going to miss so so much.&lt;/p&gt;

&lt;p&gt;The little things. The everyday chatter with colleagues, shared pet pictures and music recommendations, opinions about industry news and latest goings on in web development.&lt;/p&gt;

&lt;p&gt;The big things. The years-long collective accessibility documentation I had helped to curate in the company wiki. The client project I had spent the past year and a half guiding towards less technical debt, better accessibility, improved performance, and more efficient process. The team I was fortunate enough to work with, help guide, and learn from every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self Care
&lt;/h2&gt;

&lt;p&gt;In the same articles I had mentioned in the section above, another key point they all share is that you need to be intentional about taking care of yourself. Try to eat well and get good exercise and rest, seek good company, and practice mindfulness. It’s critical to do these things to balance against the grief, stress, and guilt.&lt;/p&gt;

&lt;p&gt;Here’s some of my self care items these first few days.&lt;/p&gt;

&lt;p&gt;I watched some guilty pleasure shows I don’t often make the time for, especially in those moments where any deep work or thought doesn’t come so easily. I gave myself permission to slow down and just enjoy something a little mindless.&lt;/p&gt;

&lt;p&gt;Went on an hour and a half walk with my wife, including through a forest preserve. Time outdoors and in nature is never wasted.&lt;/p&gt;

&lt;p&gt;A couple close friends took time out and set up an outing for all of us. It was a great time, I didn’t realize until later how much that lifted me up. I absolutely needed it and am so grateful.&lt;/p&gt;

&lt;p&gt;Being the organization and productivity nerd that I am, I got some strange sense of enjoyment cleaning up my note taking apps, updating a bunch of my contacts with photos, and ironically enough in capturing the thoughts that led to this blog post.&lt;/p&gt;

&lt;p&gt;I finally reorganized the living room entertainment center, wire management and all.&lt;/p&gt;

&lt;p&gt;Today, I’m about to get to some yard work while listening to an audiobook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;It was cathartic to jot down these thoughts and then compile them to share openly. I do feel like it’s helping me process things and start to look optimistically at the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open Invitation to Chat
&lt;/h3&gt;

&lt;p&gt;I’m sharing all this here in the hopes that it helps others going through this too to know you’re not alone, and whatever you’re feeling is a normal part of the process. Maybe it helps you make sense of your own experiences too.&lt;/p&gt;

&lt;p&gt;Open invitation to you as well to chat via email, social media DMs, whatever you like. Let’s talk about it, share some reflections and process things together, then we can move on to talk about what has us excited for the future.&lt;/p&gt;

&lt;p&gt;Speaking of, I’m some combination of excited and nervous about what I’m planning next. Working on some details and will be sharing more about that very soon. Stay tuned!&lt;/p&gt;

</description>
      <category>personal</category>
      <category>community</category>
      <category>technology</category>
    </item>
    <item>
      <title>SVG Plaid Swatch Generator</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Mon, 24 Apr 2023 14:00:00 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/svg-plaid-swatch-generator-27e3</link>
      <guid>https://dev.to/stevenwoodson/svg-plaid-swatch-generator-27e3</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VQ7u2rYS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/04/Plaid-Swatch-Generator.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VQ7u2rYS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/04/Plaid-Swatch-Generator.png" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes you’ve gotta make time to build something fun, today I whipped up a &lt;a href="https://stevenwoodson.com/plaid-generator/"&gt;Plaid Swatch Generator&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I built up my personal branding centered around the use of various plaid color combinations. I wanted all my projects to have unique color palettes but a distinctive style that was immediately apparent. For example, below are four of the logos I’ve created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P8qMly6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/04/personal-branding-1024x512.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P8qMly6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/04/personal-branding-1024x512.png" alt="Four logos that Steve had created, in clockwise order from top left is the logo for StevenWoodson.com with lots of shades of greens, then Be Inclusive with pinks and purples, then Plaid &amp;amp; Peppers with browns and reds, and finally Accessibility Solutions with reds and blues." width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Some examples of my personal/professional branding&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Early on, I was able to figure out how to get this to render as an SVG so I had more options for manipulating and so the plaid was always as performant and crisp as possible. I built &lt;a href="https://codepen.io/stevenwoodson/pen/VgJYNJ"&gt;a quick CodePen of it&lt;/a&gt; and moved on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impetus
&lt;/h2&gt;

&lt;p&gt;I had a laundry list of things to do today, but I didn’t have much motivation so I was looking around CodePen (as you do) and came across the plaid Pen I had noted above. It sparked a thought “hey this could pretty easily become a cool plaid generator, I’d just need to figure out how to change the colors dynamically.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Build
&lt;/h2&gt;

&lt;p&gt;The dynamic color updates ended up being the main chunk of work here. Once I got color input elements added to the page and wired up with a bit of JavaScript, changing the fill of the related SVG rectangles was pretty straightforward.&lt;/p&gt;

&lt;p&gt;I figured out how to get the colors to reflect in the example as you pan through the color wheel too, making choosing colors pretty intuitive as long as your browser supports more modern color picker options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy Sharing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b6cAbbLE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/04/swatch-examples-1024x1024.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b6cAbbLE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/04/swatch-examples-1024x1024.png" alt="Nine plaid swatch examples with widely ranging colors. Easily generated with this plaid swatch generator!" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Check out some of the ones I generated as I was testing!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since the main chunk of work wasn’t too bad, I set my sights on making this as easy as possible to grab and share. So I’ve added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a download button, so you can get an SVG of your creation straight away&lt;/li&gt;
&lt;li&gt;a URL auto-update to match your choices, making it easy to bookmark or share too&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Feedback?
&lt;/h2&gt;

&lt;p&gt;I have some other ideas for this, but wanted to make sure I kept it to an afternoon of hacking away. I do want to revisit it and refine with more, so give the &lt;a href="https://stevenwoodson.com/plaid-generator/"&gt;Plaid Swatch Generator&lt;/a&gt; a try and let me know what you’d like to see added!&lt;/p&gt;

</description>
      <category>svg</category>
      <category>generator</category>
      <category>frontend</category>
      <category>design</category>
    </item>
    <item>
      <title>How I Hacked My Brain to Let Myself Relax in the Evenings</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Tue, 28 Mar 2023 17:00:00 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/how-i-hacked-my-brain-to-let-myself-relax-in-the-evenings-173b</link>
      <guid>https://dev.to/stevenwoodson/how-i-hacked-my-brain-to-let-myself-relax-in-the-evenings-173b</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wMW_Ot_c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/03/How-I-Hacked-My-Brain-to-Let-Myself-Relax-in-the-Evenings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wMW_Ot_c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/03/How-I-Hacked-My-Brain-to-Let-Myself-Relax-in-the-Evenings.png" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do you ever find yourself constantly on the go, always trying to be productive? I’ve struggled with this for most of my adult life, spending evenings checking off tasks on my to-do list, a blur of writing code and responding to emails. But after a while, I realized that this wasn’t sustainable. I was regularly anxious, tired, and felt like I couldn’t just relax and recharge. I also didn’t like the feeling that family and friends were getting the leftover time not otherwise spent in this productivity cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Impetus
&lt;/h2&gt;

&lt;p&gt;I’ve been reading a whole lot of self help and productivity books the past few years. Largely as a shot of motivation to keep up the few “tricks” that have started to help me control that anxiety while still pursuing goals, but also out of a desire to find new or different tricks to try.&lt;/p&gt;

&lt;p&gt;One concept that came up often in these books is around being intentional with your time, be it making time for what you consider important, removing what you don’t consider to be essential, or building resilience to distractions.&lt;/p&gt;

&lt;p&gt;They all come down to taking charge of your time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“You only waste time if you’re not intentional about how you spend it.”&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;Jake Knapp &amp;amp; John Zeratsky, &lt;a href="https://www.indiebound.org/book/9780525572428?aff=stevenwoodson" rel="sponsored nofollow"&gt;Make Time: How to Focus on What Matters Every Day&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How I took charge of my time
&lt;/h2&gt;

&lt;p&gt;First, I had to come to terms with the fact that I have a strong need to feel productive. I’ve come to realize I can only really relax when I feel a sense of accomplishment, and know that I have a plan for continuing that into the future. So, instead of fighting against this part of myself, I decided to work with it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Remember that if you don’t prioritize your life someone else will.”&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;Greg Mckeown, &lt;a href="https://www.indiebound.org/book/9780804137386?aff=stevenwoodson" rel="sponsored nofollow"&gt;Essentialism: The Disciplined Pursuit of Less&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I started by time-boxing my productive time in the early morning before work. I found that I was most focused and least distracted in the morning, so I dedicate a couple of hours each morning to tackling my most important goal for that day.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“something magic happens when you start the day with one high-priority goal.”&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;Jake Knapp &amp;amp; John Zeratsky, &lt;a href="https://www.indiebound.org/book/9780525572428?aff=stevenwoodson" rel="sponsored nofollow"&gt;Make Time: How to Focus on What Matters Every Day&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, I started setting a daily “theme” for myself. This meant that each day, I would focus on one particular goal. For example, Monday might be dedicated to building a feature on &lt;a href="https://beinclusive.app/"&gt;Be Inclusive&lt;/a&gt;, Tuesday focuses on a blog post (like I am today), Wednesday towards reading a backlog of blog posts I’ve let pile up, etc. This helped me to avoid spending too much time on one particular area and ensured that I was making progress on all fronts.&lt;/p&gt;

&lt;p&gt;I come up with these daily goals during my weekly planning every Sunday, so I’ve got the whole week mapped out in advance. This gave me peace of mind in the evening knowing I had time set aside and a plan for it the next day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permission to Relax
&lt;/h2&gt;

&lt;p&gt;The end result? I can now relax in the evenings without feeling guilty or anxious about all the things I need to do. And, because I made it a habit to go to bed at the same time every night and wake up at the same time every morning, I’ve noticed that I’m sleeping better and have more energy throughout the day.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The people we love most should not be content getting whatever time is left over. Everyone benefits when we hold time on our schedule to live up to our values and do our share.”&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;Nir Eyal, &lt;a href="https://www.indiebound.org/book/9781948836531?aff=stevenwoodson" rel="sponsored nofollow"&gt;Indistractable&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’re someone who always needs to be productive, I encourage you to give this approach a try. It might take some time to get used to, but trust me, it’s worth it.&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>productivity</category>
      <category>selfimprovement</category>
    </item>
    <item>
      <title>Email Newsletters – My Emotional Response Spectrum and Organization Strategy</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Fri, 17 Mar 2023 14:43:08 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/email-newsletters-my-emotional-response-spectrum-and-organization-strategy-532p</link>
      <guid>https://dev.to/stevenwoodson/email-newsletters-my-emotional-response-spectrum-and-organization-strategy-532p</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F03%2FEmail-Newsletters-My-Emotional-Response-Spectrum-and-Organization-Strategy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F03%2FEmail-Newsletters-My-Emotional-Response-Spectrum-and-Organization-Strategy.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How’s that for a title? Basically, this is all about how I personally categorize newsletters, how that categorization has influenced how they’re organized, and how much attention they get as a result.&lt;/p&gt;

&lt;p&gt;I don’t think it’s a controversial statement to say that email newsletters fall on a spectrum of emotional response between “awww yeah!” to “please, no.” I’m sure I’m not the first to frame it this way, though I’ve yet to see anyone else act on that emotional response in how they organize and read through these incoming messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emotional Response Spectrum
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Awww yeah!
&lt;/h3&gt;

&lt;p&gt;These are the newsletters that help me stay in touch with the world, my industries of interest, and people I admire. The emotional response for me is one of excited anticipation, I know I’m going to spend a good deal of time on these and will generally enjoy them.&lt;/p&gt;

&lt;p&gt;The vast majority of these tend to be of a link roundup variety. People go through quite a bit of trouble sifting through the endless stream of information and share the gold, and I really appreciate that. Some others are deeper dives into current events or a particular topic that I find interesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some of my current favorites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://buttondown.email/the-a11y-project" rel="noopener noreferrer"&gt;The A11y Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ericwbailey.website/newsletter" rel="noopener noreferrer"&gt;SC 2.4.4&lt;/a&gt; by Eric Bailey&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.smashingmagazine.com/the-smashing-newsletter/" rel="noopener noreferrer"&gt;Smashing Magazine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.theskimm.com/newsletters" rel="noopener noreferrer"&gt;The Skimm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://join1440.com/" rel="noopener noreferrer"&gt;1440 Daily Digest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.readsomethinggreat.com/" rel="noopener noreferrer"&gt;Read Something Great&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Alright, thanks.
&lt;/h3&gt;

&lt;p&gt;These are the newsletters that are only occasionally relevant to me, have a certain quality that I appreciate, or otherwise are “okay” enough to open and scan in batches.&lt;/p&gt;

&lt;p&gt;Nearly all of these are the daily-ish emails from brands, I regret to admit that most haven’t been unsubscribed because of the occasional emailed coupon. Some select few (like those noted below) I mostly keep around because I enjoy their messaging or find the information useful enough to scan when I can.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://us.whogivesacrap.org/" rel="noopener noreferrer"&gt;Who Gives a Crap&lt;/a&gt; – Great brand that puts a lot of effort into making their mailers enjoyable&lt;/li&gt;
&lt;li&gt;USPS Informed Delivery&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Please, no.
&lt;/h3&gt;

&lt;p&gt;Basically any mass-email sent to me without my asking for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any newsletters I get auto-added to when I create an account, bonus 👎 points for those that don’t even make it a checkbox option in the sign up form.&lt;/li&gt;
&lt;li&gt;Unsolicited messages for products or services past the first three. I give many a pass on a few cold emails when done tactfully, can’t fault someone trying to grow their business.&lt;/li&gt;
&lt;li&gt;Extra rage bonus points to any I can’t easily unsubscribe from. One big example was the Ozy Media company, which I continued to get spammed daily until they finally shut down.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Email Organization Strategy
&lt;/h2&gt;

&lt;p&gt;I get an unbelievable amount of emails every day, most I don’t need to spend mental cycles on anywhere remotely near daily. I am – for better or worse – an inbox zero kind of guy, so I’ve honed this strategy over the years to balance the need for a tidy inbox with the need to not be sifting through emails for hours every day.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Newsletter Rules
&lt;/h3&gt;

&lt;p&gt;I have a few strongly held rules to help maintain a reasonable number of emails coming through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If I’ve not opened any messages from a particular sender &lt;strong&gt;in the last week or last 5 emails&lt;/strong&gt; , whichever comes first, then I unsubscribe. This one is occasionally hard because I _want_ to like it, but actions speak louder than intentions. Time to go.&lt;/li&gt;
&lt;li&gt;If you send &lt;strong&gt;more than 2 emails a day&lt;/strong&gt; , I’m looking for ways to adjust my preferences. If I can’t adjust email frequency I’m out. I can barely count on one hand the reasons I’d be okay with this frequency of messaging.&lt;/li&gt;
&lt;li&gt;If you send the same email multiple times, I seriously consider unsubscribing. I’m really sorry, I know email marketing is hard, but it’s not like Twitter where we’re trying to stay fresh in the ongoing stream of posts. Multiples of the same email still need to be handled separately and my patience for that is long gone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Automated Categorization
&lt;/h3&gt;

&lt;p&gt;Now that I’ve level set on the messages I’m willing to keep, I need to keep that list of messages organized. I use Gmail, though I’m sure this would work with any email organization system worth using. I set up rules for individual sender email addresses to go into one of a few newsletter specific labels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BAC’N&lt;/strong&gt; – Anything that doesn’t fit in the below two categories. I call it bac’n because, like spam, bacon is best enjoyed only occasionally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily News&lt;/strong&gt; – All the world events, financial, and other general daily news I try to consume to stay informed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Industry&lt;/strong&gt; – All the web development, accessibility, design system, technology, etc. news that keeps me up to date on what’s trending around me professionally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F03%2FScreen-Shot-2023-03-11-at-9.24.28-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.walnutcreekcreative.com%2Fstevewoodson%2Fwp-content%2Fuploads%2Fsites%2F3%2F2023%2F03%2FScreen-Shot-2023-03-11-at-9.24.28-AM.png" alt="My mailing list labels in Gmail, first is "&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;My newsletter inboxes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These emails are set to skip the inbox and go directly to the selected folder. This way I don’t get notifications of new messages, and I’m able to ignore them until I’m ready. Which brings me to…&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing my Attention
&lt;/h3&gt;

&lt;p&gt;Great, now I’ve got stacks of organized emails by category. Now, what do I do with them?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BAC’N emails are scanned as quickly as I can manage, I tend to do this about once a week. I take note of sales and any links to articles I may want to read in more detail.&lt;/li&gt;
&lt;li&gt;Daily News I try (and often fail) to keep up with every morning. If I miss a few I read the latest and then scan the older ones for any info I may have missed. Given that they tend to be daily news, it’s often updates on ongoing stories so this has worked out pretty well.&lt;/li&gt;
&lt;li&gt;Industry emails tend to take me the longest to get through because there are a lot of collections of links to long form articles. I try to chip away at these as I have some time but honestly this is the hardest one to manage because I wanna read _it all_ but it takes a lot of time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Archives
&lt;/h3&gt;

&lt;p&gt;This one may seem a bit weird, but hear me out.&lt;/p&gt;

&lt;p&gt;I keep an embarrassingly long history of newsletter emails, like months and sometimes years worth. This has served me well enough in the past that I’ve stuck with it, being able to refer back to previous emails from the same sender helps me in a few ways.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Big Sale” emails&lt;/strong&gt; – We all get them, the “blowout” sale messages for 20% off and the like. Well, I can see that you just had a similar sale a few months ago for 30% off, so I’ll wait. Thanks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frequency changes&lt;/strong&gt; – Sometimes companies change their strategies and start sending more/less emails. If it gets to be annoying and I’m unsure why because I’ve been subscribed forever, this sometimes helps explain. A company that used to email weekly starts sending a couple messages a day, for example.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Newsletter Rules&lt;/strong&gt; – All three of my newsletter rules requires that I have some reference to several recent messages, can only do that by saving them!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overthinking it?
&lt;/h2&gt;

&lt;p&gt;Sometimes I do think “this is a lot of consideration for email newsletters”. But I’ve come to the conclusion that the occasional time I put into curating saves me a whole lot more in the long run. Besides, if you don’t control where your attention goes someone else will.&lt;/p&gt;

</description>
      <category>email</category>
      <category>marketing</category>
      <category>productivity</category>
    </item>
    <item>
      <title>CloudFront Function for basic auth, redirect, and serving from S3</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Wed, 15 Mar 2023 14:08:28 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/cloudfront-function-for-basic-auth-redirect-and-serving-from-s3-3b6n</link>
      <guid>https://dev.to/stevenwoodson/cloudfront-function-for-basic-auth-redirect-and-serving-from-s3-3b6n</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7hqBdMXn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/03/CloudFront-Function-for-basic-auth-redirect-and-serving-from-S3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7hqBdMXn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://content.walnutcreekcreative.com/stevewoodson/wp-content/uploads/sites/3/2023/03/CloudFront-Function-for-basic-auth-redirect-and-serving-from-S3.png" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently had the opportunity to rethink how some CDN customizations were happening within an Amazon AWS managed project. Previously, we had used a couple Lambda@Edge functions to perform some light modifications during the &lt;strong&gt;Viewer request&lt;/strong&gt; and &lt;strong&gt;Origin request&lt;/strong&gt; cache behaviors. We had largely inherited the setup so hitting the reset button with some needed changes seemed a good idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial CloudFront Function Research
&lt;/h2&gt;

&lt;p&gt;Enter &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html"&gt;CloudFront Functions&lt;/a&gt;, I had actually not heard of them previously but apparently they’ve been around &lt;a href="https://aws.amazon.com/blogs/aws/introducing-cloudfront-functions-run-your-code-at-the-edge-with-low-latency-at-any-scale/"&gt;since 2021&lt;/a&gt;. After getting up to speed reading up on the use case it seemed an ideal evolution from &lt;a href="mailto:Lambda@Edge"&gt;Lambda@Edge&lt;/a&gt;. The parts I was most excited about was reading the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;offers submillisecond startup times, scales immediately to handle millions of requests per second, and is highly secure&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our use case needed the following:&lt;/p&gt;

&lt;p&gt;A huge thank you to Joshua Lyman for writing up &lt;a href="https://www.joshualyman.com/2022/01/add-http-basic-authentication-to-cloudfront-distributions/"&gt;Add HTTP Basic Authentication to CloudFront Distributions&lt;/a&gt;, it ended up being a great head start to basic authentication I needed (item #1 on the list).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Basic Authentication for all lower environments&lt;/strong&gt; , because we don’t want public access to content that’s still a work in progress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More control over redirects&lt;/strong&gt; , because we’re using a JavaScript framework we weren’t able to control the response status as well as we would have liked (301 instead of 302 for example)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add index.html to request URLs that don’t include a file name&lt;/strong&gt; , also because we’re using a statically generated site based on a JavaScript framework.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Gotchas
&lt;/h3&gt;

&lt;p&gt;I was initially surprised – coming from the Lambda functions mentality – that I wasn’t able to use a couple of the JavaScript niceties like the Nullish coalescing operator (??), Optional chaining operator (?.), and Conditional (ternary) operator. Found out that that’s because &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html#writing-functions-javascript-features-restricted-features"&gt;CloudFront functions only support ES 5.1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I was also pleasantly surprised to see a high focus on optimization and utilization, you even get a handy “&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/test-function.html?icmpid=docs_cf_help_panel#compute-utilization"&gt;Compute Utilization&lt;/a&gt;” score every time you test so you can keep tabs on ensuring it’s comfortably within the max allowed time.&lt;/p&gt;

&lt;p&gt;While much of these weren’t an issue for me, I also found out that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic code evaluation (&lt;code&gt;eval&lt;/code&gt; for example) and Timers are not supported.&lt;/li&gt;
&lt;li&gt;The maximum memory assigned to CloudFront Functions is 2MB&lt;/li&gt;
&lt;li&gt;CloudFront Functions only respond to Viewer triggers whereas Lambda@Edge can work with both Viewer and Origin triggers. In my case I was able to merge the code we were using in both situations without any issue, but your mileage may vary.&lt;/li&gt;
&lt;li&gt;CloudFront Functions only support JavaScript (ES 5.1 as noted above), Lambda@Edge supports Node.js and Python too.&lt;/li&gt;
&lt;li&gt;CloudFront Functions do not have access to the network or filesystem.&lt;/li&gt;
&lt;li&gt;CloudFront Functions can only manipulate HTTP headers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Overall cheaper, and there’s a free tier available too.&lt;/li&gt;
&lt;li&gt;Building &amp;amp; testing are so much faster because you can do so directly within CloudFront, no more saving, creating a version, attributing that to the CloudFront cache behavior and deploying to test!&lt;/li&gt;
&lt;li&gt;Speed and scalability are amazing, you can support 10,000,000 requests per _second_ or more compared to up to 10,000 requests per second per Region for &lt;a href="mailto:Lambda@Edge"&gt;Lambda@Edge&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re interested in more notes to help you choose which way to go, this &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions.html#edge-functions-choosing"&gt;Choosing between CloudFront Functions and Lambda@Edge&lt;/a&gt; section was really helpful for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;Let’s get to work!&lt;/p&gt;

&lt;p&gt;Final source&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var authRedirect = {
  statusCode: 401,
  statusDescription: "Unauthorized",
  headers: {
    "www-authenticate": {
      value: 'Basic realm="Enter credentials to access this secure site"',
    },
  },
};

/**
 * Basic Authentication
 *
 * On all lower environment (dev &amp;amp; qa) we need basic authentication so general
 * public cannot access these sites. Bypass on prod and if authentication is
 * already secured.
 *
 * @param authorization - Incoming request authorization value
 * @param host - Incoming request host value
 * @returns boolean - Whether this request is authenticated
 */
function authCheck(authorization, host) {
  var productionHosts = ["mywebsite.com", "www.mywebsite.com", "blog.mywebsite.com"];

  // The Base64-encoded Auth string `secretuser:secretpass` that should be present.
  var expected = "Basic c2VjcmV0dXNlcjpzZWNyZXRwYXNz";

  // If this is a production website, we do not want to force any authentication
  if (productionHosts.indexOf(host) &amp;gt; -1) {
    return true;
  }

  // If an Authorization header is supplied and it's an exact match
  if (authorization &amp;amp;&amp;amp; authorization.value === expected) {
    return true;
  }

  //Not production, auth header is either missing or failed to match
  return false;
}

/**
 * Redirect Check
 *
 * @param host - Incoming request host value
 * @param requestURI - Incoming request URI
 * @returns object|false - Returns an object containing the redirect details or false if no redirect necessary
 */
function redirectCheck(host, requestURI) {
  redirectPaths = {
    // '[FROM]': {'Location': '[TO]', 'status': [301|302]}},
    "/old-page-1": { Location: "/new-page-1/", status: 301 },
    "/old-page-2": { Location: "/new-page-2/", status: 301 },
    "/old-page-3": { Location: "/new-page-3/", status: 302 },
  };

  if (Object.keys(redirectPaths).indexOf(requestURI) &amp;gt;= 0) {
    var redirectTo = redirectPaths[requestURI];
    return redirectTo;
  }

  return false;
}

/**
 * Redirect Response
 *
 * @param redirectTo - object containing the redirect location and status details
 * @returns object - response object with redirect details
 */
function redirectResponse(redirectTo) {
  return {
    statusCode: redirectTo.status,
    statusDescription: "Moved",
    headers: {
      location: { value: redirectTo.Location },
    },
  };
}

/**
 * URL Rewrite
 *
 * Appends the /index.html to requests that don’t include a file name or extension
 * in the URL. Useful for single page applications or statically generated websites
 * that are hosted in an Amazon S3 bucket.
 *
 * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/example-function-add-index.html
 * @param uri - Incoming request URI
 * @returns
 */
function uriRewrite(uri) {
  // Check whether the URI is missing a file name.
  if (uri.endsWith("/")) {
    return (uri += "index.html");
  }
  // Check whether the URI is missing a file extension.
  else if (!uri.includes(".")) {
    return (uri += "/index.html");
  } else {
    return uri;
  }
}

/**
 * Primary Handler
 *
 * Checks for authentication, then redirect, then URL rewriting
 *
 * @param event
 * @returns
 */
function handler(event) {
  var authorization = event.request.headers.authorization;
  var host =
    event.request.headers.host &amp;amp;&amp;amp; event.request.headers.host.value
      ? event.request.headers.host.value.toLowerCase()
      : "";
  var requestURI = event.request.uri.replace(/\/$/, "");

  if (authCheck(authorization, host) === false) {
    return authRedirect;
  }

  var redirect = redirectCheck(host, requestURI);
  if (redirect !== false) {
    return redirectResponse(redirect);
  }

  event.request.uri = uriRewrite(requestURI);

  return event.request;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>cloudfront</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Conserving Sentry Transactions by Ignoring Laravel Routes</title>
      <dc:creator>Steven Woodson</dc:creator>
      <pubDate>Tue, 28 Feb 2023 22:37:27 +0000</pubDate>
      <link>https://dev.to/stevenwoodson/conserving-sentry-transactions-by-ignoring-laravel-routes-12dn</link>
      <guid>https://dev.to/stevenwoodson/conserving-sentry-transactions-by-ignoring-laravel-routes-12dn</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11jo26t45c4m3ga8qgja.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11jo26t45c4m3ga8qgja.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yet another case of trying to find an answer to a very particular problem, coming up empty, solving it myself, and posting about it for future generations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update 2023-04-01
&lt;/h2&gt;

&lt;p&gt;Thanks to &lt;a href="https://twitter.com/cleptric" rel="noopener noreferrer"&gt;Michi Hoffmann&lt;/a&gt;, this is now even easier to manage with a built in &lt;code&gt;ignore_transactions&lt;/code&gt; configuration option now available as of &lt;a href="https://github.com/getsentry/sentry-php/releases/tag/3.17.0" rel="noopener noreferrer"&gt;release 3.17.0 of sentry-php&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Now, instead of using the &lt;code&gt;before_send_transaction&lt;/code&gt; config I swapped to using &lt;code&gt;ignore_transactions&lt;/code&gt; and my code changed to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'ignore_transactions' =&amp;gt; ['/_debugbar', '/monitoring', '/pleaseignoreme'],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is the original post for posterity, &lt;strong&gt;but with the addition above that solution is no longer necessary&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I have a couple Laravel PHP routes that are triggered quite often, both are very specific to other site monitoring (is the site down?) and session management (am I still logged in?). These being utility routes that don’t serve a user-facing purpose, I don’t need any additional logging details about them from Sentry.&lt;/p&gt;

&lt;p&gt;Can I prevent the transaction from being sent to Sentry before it even leaves my server? I tried searching terms like “sentry laravel ignore route” and “sentry control transactions from PHP” to no avail so I got to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;In my searches, I did find &lt;a href="https://docs.sentry.io/platforms/php/guides/laravel/configuration/filtering/#using-platformidentifier-namebefore-send-transaction-" rel="noopener noreferrer"&gt;this helpful documentation for before_send_transaction&lt;/a&gt; specifically for Laravel. It’s an optional addition to the &lt;code&gt;/config/sentry.php&lt;/code&gt; configuration. In it, you can define any logic you want to return &lt;code&gt;null&lt;/code&gt; in preferred situations. This null return will lead to the event being discarded. Bingo!&lt;/p&gt;

&lt;p&gt;Here’s the code I worked up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'before_send_transaction' =&amp;gt; function (
    \Sentry\Event $transaction
): ?\Sentry\Event {
    $ignore = ['_debugbar', 'monitoring', 'pleaseignoreme'];
    $request = $transaction-&amp;gt;getRequest();
    $check = array_filter($ignore, function ($url) use ($request) {
        if (stripos($request['url'], $url) !== false) {
            return true;
        }
    });

    if (count($check) &amp;gt; 0) {
        return null;
    }

    return $transaction;
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s what it’s doing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$ignore&lt;/code&gt; is the array of URL key terms I want to ignore, this is all you need to edit. Add any URL segments that you’d prefer not to be logged by Sentry and you good to go&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$request&lt;/code&gt; is the request details coming from the packaged &lt;code&gt;$transaction&lt;/code&gt; generated by Sentry. In it is the full URL that we use to test against&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$check&lt;/code&gt; was my method of iterating through the ignore list and checking for a match. This filters the &lt;code&gt;$ignore&lt;/code&gt; array down to just matches.&lt;/li&gt;
&lt;li&gt;We then see if the count of &lt;code&gt;$check&lt;/code&gt; is greater than zero, if so then one of the routes matched and we want to return &lt;code&gt;null&lt;/code&gt; to ignore this transaction. Otherwise, return as normal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  That’s it!
&lt;/h2&gt;

&lt;p&gt;Pretty simple, but it took a bit of digging through documentation to find &lt;code&gt;before_send_transaction&lt;/code&gt; and through the Sentry package to see what &lt;code&gt;$transaction&lt;/code&gt; contained so I’m hoping to spare the next dev that bit of trouble.&lt;/p&gt;

&lt;p&gt;Onward and upward! 🚀&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>sentry</category>
      <category>php</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
