<?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: Braydon Coyer</title>
    <description>The latest articles on DEV Community by Braydon Coyer (@braydoncoyer).</description>
    <link>https://dev.to/braydoncoyer</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%2F487155%2Fd537e5be-04cd-45cd-9746-24ec189f53d9.jpeg</url>
      <title>DEV Community: Braydon Coyer</title>
      <link>https://dev.to/braydoncoyer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/braydoncoyer"/>
    <language>en</language>
    <item>
      <title>5 Basic Tips for Angular Unit Testing</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Tue, 31 May 2022 13:23:52 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/5-basic-tips-for-angular-unit-testing-38b3</link>
      <guid>https://dev.to/braydoncoyer/5-basic-tips-for-angular-unit-testing-38b3</guid>
      <description>&lt;p&gt;Unit testing provides assurance that your application works as intended by running an automated piece of code that invokes a unit of work (a separate piece of code). The test passes or fails based on an assumption about the behavior of that unit of work (we call this the code under test).&lt;/p&gt;

&lt;p&gt;While unit testing across frontend frameworks hold the same core principals, it isn’t surprising that unit testing in Angular holds some key differences. Unit testing is a skill that requires time and patience to develop. If you’re learning how to write unit tests in Angular, here are 5 basic tips to accelerate your learning: understand Angular dependencies, test in isolation, write granular tests, test logic rather than the DOM and write your tests before the implementation code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand Angular Dependencies and Modules
&lt;/h2&gt;

&lt;p&gt;The first tip is to take time to understand how Angular handles dependencies. This tip is a prerequisite to the next tip; you’ll need to identify dependencies in order to properly test in isolation.&lt;/p&gt;

&lt;p&gt;Angular’s Module architecture is a bit unique, and probably one of the hardest parts for beginners to pick up. Angular Modules are built on top of ES Modules - a solution to sharing code between files. A module, at its core, is simply a way to import and export code for other files to consume. There are differences in the way ES Modules and Angular Modules work, but the core idea remains the same.&lt;/p&gt;

&lt;p&gt;Angular Modules list dependencies that other code (components, services, etc) can use. For example, in order to use and consume a reusable button component in your application, it needs to be registered in a corresponding Angular Module. If it’s not, the compiler will throw an error.&lt;br&gt;
Why is this important? That brings us to the second tip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test in Isolation
&lt;/h2&gt;

&lt;p&gt;Testing in isolation means that the unit that is being tested should be separate from other parts of the application. What does this mean when we talk about unit testing in Angular? Whatever you're testing (whether that be a component, service, pipe, etc) should have all other dependencies separated/mocked. If you think it through, this makes sense. &lt;/p&gt;

&lt;p&gt;We don’t want to test the entire application, we only want to test a specific slice of it. That’s the whole point of unit testing!&lt;/p&gt;

&lt;p&gt;If you don't test in isolation, you'll end up with hours of headache as you sift through ambiguous console errors trying to figure out why (and where!) your tests are failing.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, in order to test in isolation, you need to mock out dependencies. This is why it’s very important to understand how Angular handles dependencies. A dependency can be a component you’re consuming, a service that is injected, or a handful of other things.&lt;/p&gt;

&lt;p&gt;Thankfully, mocking is very straightforward. If you want to learn how to mock an Angular component, &lt;a href="https://braydoncoyer.dev/blog/mocking-components-in-angular"&gt;read my other article&lt;/a&gt;. If you want to mock an Angular service, I wrote &lt;a href="https://braydoncoyer.dev/blog/mocking-services-in-angular"&gt;another little article here&lt;/a&gt; to show you how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write Granular Unit Tests
&lt;/h2&gt;

&lt;p&gt;Thirdly, I recommend that you write small, independent unit test cases. It can be tempting to write a catch-all test scenario where you call an implementation function and make multiple expectations in a single unit test. Failing tests that have multiple assertions make it difficult to understand what went wrong.&lt;/p&gt;

&lt;p&gt;Rather than falling into the catch-all single test case scenario, identify how a single unit can be split into multiple test cases (if the situation calls for it). For example, if a component function subscribes to a service and updates local component state with the result, you can easily create two or three test cases instead of a single bloated test.&lt;/p&gt;

&lt;p&gt;For more information about what makes an Angular unit test valuable, &lt;a href="https://braydoncoyer.dev/blog/what-makes-a-unit-test-valuable"&gt;click here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test logic, not the DOM
&lt;/h2&gt;

&lt;p&gt;This tip can be a bit controversial. It’s possible to write unit tests that search the DOM for elements, perform an action (like a click), and assert that certain behavior was executed.&lt;br&gt;
While I do think that some situations calls for this type of structure, it should not be the norm. If you find yourself writing a bunch of DOM queries in your tests, you may want to delegate those tasks to an End-to-End (E2E) test.&lt;/p&gt;

&lt;p&gt;Consider the classic calculator example which contains many buttons that perform various mathematical operations. Each time a button is clicked, data is manipulated and a new number or sum is displayed on the screen. This is a perfect scenario for a unit test! Data is changing with each button click; the calculator produces a certain output when given a certain input.&lt;/p&gt;

&lt;p&gt;On the other hand, it’s not uncommon for a button to navigate the user to a different page, or to make something else appear or disappear. Rather than solely changing data, these scenarios represent application functionality and is a great opportunity to write an E2E test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test First, Code Second (Test Driven Development)
&lt;/h2&gt;

&lt;p&gt;Finally, and perhaps most important, discipline yourself to write your unit test cases first before you write the component or service logic. Does that sound odd? It’s okay if it does - it’s a bit backwards in a sense.&lt;/p&gt;

&lt;p&gt;Writing test cases first is called Test Driven Development (TDD). Rather than the implementation code influencing how the unit test is written, TDD allows the test scenario to drive the implementation of the code. Because of this, code written in a TDD pattern generally is cleaner and less bloated.&lt;/p&gt;

&lt;p&gt;Test Driven Development has a few rules and conventions that go along with it. If you’re want to learn more about TDD, &lt;a href="https://www.browserstack.com/guide/what-is-test-driven-development"&gt;BrowserStack has a detailed explanation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Remember, unit testing in this fashion takes time to learn; it’s a skill that you must develop. I encourage you to start small and experience the benefits that TDD provides.&lt;/p&gt;

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

&lt;p&gt;In this article, we looked at five general tips for unit testing in Angular. If you’re starting to learn how to test in Angular and feel overwhelmed, remember that unit testing is a skill that takes time to develop.&lt;/p&gt;

&lt;p&gt;My hope is that by understanding Angular dependencies, testing in isolation, writing granular test cases, testing the underlying logic rather than the DOM, and giving Test Driven Development a try, you’ll have a better toolbox to successfully accelerate your learning and have the skills needed to write tests that provide assurance that your code behaves as expected.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>angular</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Learn How to Click a Button when Angular Unit Testing</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Fri, 27 May 2022 23:45:20 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/learn-how-to-click-a-button-when-angular-unit-testing-3mb9</link>
      <guid>https://dev.to/braydoncoyer/learn-how-to-click-a-button-when-angular-unit-testing-3mb9</guid>
      <description>&lt;p&gt;Buttons play a large part in the user experience of your user interface. Angular makes working with buttons extremely easy, but perhaps you’ve hit a wall when your mindset switches to testing. Should you have unit test cases for button clicks in your Angular application? Is it really that important? And if so, how would you go about testing that scenario? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There are two prominent approaches when it comes to writing unit tests for button clicks in Angular: either you search the DOM for the button, perform an actual click and verify the expected behavior, or you simply call the component-code that will run when the button is clicked. Both options have their pros and cons. In this article, we’ll investigate each testing route thoroughly and look at various examples so that you understand everything you need to know about how to write unit tests for button clicks in Angular.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Why and when should you unit test button clicks in Angular?
&lt;/h2&gt;

&lt;p&gt;If you have some experience with automated testing, it wouldn’t be surprising if you’re wondering if a button click is something that even needs to be handled with a unit test in Angular. Perhaps in the past you’ve opted to forgo a unit test and defer that responsibility to an E2E (End-to-End) test. There isn’t anything wrong with that decision - E2E tests validate functionality by performing tests from a user’s experience by simulating real user scenarios in the application. &lt;/p&gt;

&lt;p&gt;A unit test, on the other hand, is a bit more granular. It’s an automated piece of code that invokes a unit of work (a separate piece of code) in the application and is usually approached from a black-box perspective. The test passes or fails based on an assumption or expectation about the behavior of that unit of work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🤔 If you’re interested in learning more about writing well-structured and valuable tests in Angular, &lt;a href="https://braydoncoyer.dev/blog/what-makes-a-unit-test-valuable"&gt;check out my other article&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A unit test is almost always written using a testing framework, allowing it to be written efficiently and run quickly. If you generate a new Angular project with the Angular CLI, your application comes with Jasmine and Karma (the testing framework and runner) out of the box.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📢 This article assumes that you’re using Jasmine and Karma for your unit testing. However, if you’re using another testing framework like Jest, the approach will be almost identical.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Angular Button testing: Data Validation or Application Functionality
&lt;/h3&gt;

&lt;p&gt;There isn’t a set rule on if buttons should be covered by a unit test. In fact, the decision of whether to write a unit test for a button click ultimately comes down to personal opinion. If you prefer to defer that functionality to an E2E test, that’s great! But in my opinion, there are certain situations where a button click unit test provides valuable reassurance in an Angular application.&lt;/p&gt;

&lt;p&gt;Consider the classic calculator example which contains many buttons that perform various mathematical operations. Each time a button is clicked, data is manipulated and a new number or sum is displayed on the screen. This is a perfect scenario for a unit test! Data is changing with each button click; the calculator produces a &lt;strong&gt;&lt;em&gt;certain output&lt;/em&gt;&lt;/strong&gt; when given a &lt;strong&gt;&lt;em&gt;certain input&lt;/em&gt;&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;On the other hand, it’s not uncommon for a button to navigate the user to a different page, or to make something else appear or disappear. Rather than solely changing data, these scenarios represent application &lt;strong&gt;&lt;em&gt;functionality&lt;/em&gt;&lt;/strong&gt; and is a great opportunity to write an E2E test.&lt;/p&gt;

&lt;p&gt;With this in mind, does your situation call for a unit test or would it be best to create an E2E test?&lt;/p&gt;

&lt;p&gt;Recall that there are generally two approaches to writing a unit test for buttons: either you locate the button on the DOM and simulate a click, or you test against the code that will be run when the button is clicked by a user. Let’s look at the more complex example first.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Test a Button Click in Angular
&lt;/h2&gt;

&lt;p&gt;This approach may be useful in some situations, but the act of delegating a unit test to browse the DOM to find the button and perform a click is a point of contention. The unit test still makes expectations around the notion of what’s supposed to happen when the button is clicked, but many argue that performing the click is the responsibility of an E2E test.&lt;/p&gt;

&lt;p&gt;Regardless, locating the button on the DOM is a trivial task, especially when you isolate the button into a reusable component. The following is an example of just that - a reusable isolated button component that, as mentioned prior, assumes you have TestBed configured properly with Jasmine and Karma.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Component: Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComponentFixture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ButtonComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ButtonComponent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configureTestingModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;ButtonComponent&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;compileComponents&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;fixture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ButtonComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should call onButtonClick when clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fakeAsync&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onButtonClick&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onButtonClick&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The TypeScript file for this button component has a function called &lt;code&gt;onButtonClick&lt;/code&gt; that is bound to the &lt;code&gt;button&lt;/code&gt; element in the template. This test first spies on the local function, locates the button and then performs a click. After a simulated passage of time with &lt;code&gt;tick()&lt;/code&gt;, we make an assertion that the &lt;code&gt;onButtonClick&lt;/code&gt; function was called.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📣 Events can be tested using &lt;code&gt;async&lt;/code&gt; / &lt;code&gt;fakeAsync&lt;/code&gt; function imported from &lt;code&gt;'@angular/core/testing’&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notice that the button was located on the DOM using &lt;code&gt;querySelector&lt;/code&gt; and passing &lt;code&gt;button&lt;/code&gt; as an argument. This works fine in an isolated component like this, but in different scenarios where multiple &lt;code&gt;button&lt;/code&gt; elements may exist, you need to use something that provides more specificity. &lt;/p&gt;

&lt;p&gt;This example is pretty straightforward - we simply verify that the function is called when the button is clicked. But we can take this further. Let’s look at the &lt;code&gt;onButtonClick&lt;/code&gt; function and see what else can be tested.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;buttonClicked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="nx"&gt;onButtonClick&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttonClicked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this is a reusable button component, it makes sense to delegate the responsibility of functionality to whichever component consumes it. In order for the parent component to identify when the button has been clicked, it can listen to an event emitter inside the button component (&lt;code&gt;buttonClicked&lt;/code&gt;). In response for the event being emitted, the parent component calls a local function to, for example, perform a mathematical operation in the calculator example above. &lt;/p&gt;

&lt;p&gt;From a testing perspective, it would provide value to have a unit test that ensures that the event is emitted when the &lt;code&gt;onButtonClick&lt;/code&gt; function is called. Here’s what that may look like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should emit the event when #onButtonClicked is called&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emitSpy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttonClickEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onButtonClick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emitSpy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spies come to the rescue here again. The &lt;code&gt;emit&lt;/code&gt; function lives on the &lt;code&gt;buttonClickEvent&lt;/code&gt; object, and the test simply verifies that the spy was called when the code under test is executed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about other situations?
&lt;/h3&gt;

&lt;p&gt;There may be other situations in a reusable button component where unit tests could prove useful and provide reassurance that it’ll continue to work in the future with additional changes. We won’t be discussion or covering those scenarios in this article, though.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Test Button Functionality in Angular
&lt;/h2&gt;

&lt;p&gt;Now that the reusable button component has a supporting test cases, let’s turn our attention to situations where it may prove beneficial to test local code that is wired up to that button component.&lt;/p&gt;

&lt;p&gt;Recall that the reusable button component emits an event when clicked. Other parts of our application can listen to that event, and call a local function to perform isolated operations.&lt;/p&gt;

&lt;p&gt;Continuing our calculator idea from earlier, here’s one example where we consume the reusable button component and listen to the &lt;code&gt;buttonClickEvent&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;app-button&lt;/span&gt; &lt;span class="na"&gt;(buttonClickEvent)=&lt;/span&gt;&lt;span class="s"&gt;"add(5)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  5
&lt;span class="nt"&gt;&amp;lt;/app-button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We already have unit tests that locate the button on the DOM and initiate a click event, so there’s no need to test that here in the parent component. Instead, let’s look directly at the &lt;code&gt;add&lt;/code&gt; function and see if there’s anything inside worth testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toAdd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;toAdd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very basic and straightforward example specifically for this article. This function mutates data, and if you recall from earlier, this is a great opportunity to add supporting test cases.&lt;/p&gt;

&lt;p&gt;But what do you test? &lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;add&lt;/code&gt; function, we would write a test that ensures the &lt;code&gt;total&lt;/code&gt; class variable increments with the appropriate value passed to the function. This example is pretty simple, but the skill of determining what to test is something that comes with practice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📣 If you’re wanting to develop the skill of identifying test cases, consider reading my article titled &lt;a href="https://braydoncoyer.dev/blog/the-gumball-machine-how-to-quickly-identify-unit-test-cases"&gt;The Gumball Machine - How To Quickly Identify Unit Test Cases&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s what the test would look like. Again this assumes you have the test suite set up correctly with TestBed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should add 5 to the calculator total&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;expectedTotal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedTotal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we call the &lt;code&gt;add&lt;/code&gt; function directly in the parent component test. Remember, we already have assurance that the button works as intended when clicked, so in this case we simply call the code under test.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ It’s never good to trust that a test is working as intended without seeing it fail. I recommend making an intentional error in the &lt;code&gt;add&lt;/code&gt; function (not the test) to see if the test fails when it needs to fail.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;In this article, we investigated the different ways to test button clicks in Angular. One option is to write a unit test that locates the button on the DOM, perform a click and assert that something happened in the test. On the other hand, it may be appropriate to simply call function that is called when the button is clicked and write expectations based on what occurs in the code under test. &lt;/p&gt;

&lt;p&gt;Ultimately, it comes down to your personal preference. Whichever route you choose to take, I hope this article has proven helpful and shown you how to write unit tests for button clicks in Angular.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>testing</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Enable Autocomplete for Tailwind CSS in VSCode</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Wed, 27 Apr 2022 14:30:22 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/enable-autocomplete-for-tailwind-css-in-vscode-4748</link>
      <guid>https://dev.to/braydoncoyer/enable-autocomplete-for-tailwind-css-in-vscode-4748</guid>
      <description>&lt;p&gt;If you’ve recently found Tailwind CSS, you’re no doubt amazed at how quickly you can write CSS! But you may be wondering if there is a way to add autocomplete in VSCode so you can write Tailwind CSS even more quickly and more efficiently?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Absolutely! The Tailwind Labs team has developed and released an official plugin that adds autocomplete to your VSCode environment, and it only takes a few clicks to enable! The Autocomplete extension allows you to quickly apply appropriate utility class names to your markup, removing the guess work and providing relevant information at a glance. Furthermore, the plugin also supports linting, hover previews and syntax highlighting! Let’s quickly add autocomplete to VSCode!&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Install the Tailwind CSS VSCode Plugin
&lt;/h2&gt;

&lt;p&gt;Adding autocomplete to VSCode for Tailwind is easier than you think. Head to the Visual Studio Code Marketplace and search for &lt;a href="https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss"&gt;Tailwind CSS IntelliSense&lt;/a&gt;. Alternatively, you can open VSCode and click on the Extensions tab in the sidebar and search for the plugin. &lt;/p&gt;

&lt;p&gt;Once you’ve found the plugin in VSCode, click the Install button to install the plugin globally in your workspace. From here, the plugin should &lt;strong&gt;&lt;em&gt;automagically&lt;/em&gt;&lt;/strong&gt; ✨ work so long as you have Tailwind installed and a &lt;code&gt;tailwind.config.js&lt;/code&gt; or &lt;code&gt;tailwind.config.cjs&lt;/code&gt; file in your repository. If you aren’t seeing the changes take affect, you may need to restart VSCode and try again.&lt;/p&gt;

&lt;p&gt;Unlike some other VSCode extensions for Tailwind, this plugin is developed and maintained by the Tailwind team! You can expect this extension to be updated as new major versions of Tailwind are released with new features!&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Autocomplete
&lt;/h2&gt;

&lt;p&gt;Writing CSS with Tailwind is already insanely effective, but it can be even better if you use VSCode with the autocomplete plugin! With autocomplete added in VSCode for Tailwind, you can start to type in a utility class and a pop up suggests a few options to choose from. This is extremely helpful for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You may not need to fully type the utility class. Simply start to type the class name and the intelligent autocomplete will suggest a few options. You can use the arrow keys to navigate up and down in the suggestion list, and hit Enter or Return on your keyboard to add the class name into the markup. It’s super quick!&lt;/li&gt;
&lt;li&gt;If you can’t remember which Tailwind class to use, the autocomplete can help you narrow down and find the correct utility.&lt;/li&gt;
&lt;li&gt;The autocomplete can be very useful, especially when selecting color-based utility classes! Not only can you quickly scan through the color palette, but the autocomplete also gives you a preview of the color in the list!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Identify Tailwind CSS Errors with Linting
&lt;/h2&gt;

&lt;p&gt;Using Tailwind out-of-the-box provides a fast way to write CSS in your application. But the real power of Tailwind begins to shine when you extend the library to use your own design system. Supplying your own color palette, extending the default typography settings and even using the &lt;code&gt;@apply&lt;/code&gt; feature can help set your site above others made with the library and provide a custom solution to fit any development need. &lt;/p&gt;

&lt;p&gt;As you extend the library, you run the risk of configuration code that produces errors at compile time. Without the Tailwind VSCode extension installed, you may not know that an error has occurred until the site is built. &lt;/p&gt;

&lt;p&gt;Thankfully, the plugin helps guard against error and small mistakes by providing real-time linting, allowing you to not only quickly identify when something is wrong and should be changed, but also get an idea of how to make the code compliant. &lt;/p&gt;

&lt;p&gt;For example, when using &lt;code&gt;@apply&lt;/code&gt; and stringing together class names in an external &lt;code&gt;.css&lt;/code&gt; file, I occasionally use a text-color that I have not defined in my &lt;code&gt;tailwind.config.js&lt;/code&gt; file. If (and when) these small mistakes occur, the Tailwind VSCode plugin puts a red squiggly underneath the offender, making it easy for me to see the error and quickly fix before saving the file and beginning a refresh of the development server.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Use Hover Previews to Peek at The Underlying CSS
&lt;/h2&gt;

&lt;p&gt;Tailwind CSS is a CSS framework, allowing you to write your styles without leaving your markup. Unlike other CSS frameworks like Bootstrap where you can learn Bootstrap-specific class names, Tailwind suggests that the user understands CSS as a prerequisite. Generally, Tailwind won’t aid developers write CSS faster if they don’t understand CSS in the first place. &lt;/p&gt;

&lt;p&gt;However, with the Tailwind CSS autocomplete plugin installed in VSCode, it is possible to peek into the underlying CSS implementation. &lt;strong&gt;Simply hover over a utility class in your markup and the extension will provide a window showing the actual CSS happening behind the scenes.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Why is this helpful?&lt;/p&gt;

&lt;p&gt;First, having the ability to look at the CSS behind the utility class can be helpful in better understanding CSS and proper syntax. Second, for some non-utility Tailwind classes like the &lt;code&gt;prose&lt;/code&gt; class, you can peek at all of the CSS rules and statements that are applied to the code. This may not be super useful in your day-to-day operations, but I’ve already run into a few situations where this has helped me solve an issue or quickly see which CSS unit is applied to the elements on the DOM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XqcZVwta--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1651069540/prose_f8wbn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XqcZVwta--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1651069540/prose_f8wbn2.png" alt="https://res.cloudinary.com/braydoncoyer/image/upload/v1651069540/prose_f8wbn2.png" width="880" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What about JetBrains IDEs?
&lt;/h2&gt;

&lt;p&gt;Can you get autocomplete with Tailwind CSS if VSCode isn’t your IDE of choice? Absolutely! &lt;/p&gt;

&lt;p&gt;First, make sure you have Tailwind installed in your project and a &lt;code&gt;tailwind.config.js&lt;/code&gt; file at the root of your repository. Next, open the &lt;strong&gt;Settings/Preferences | Plugins&lt;/strong&gt; page and enable the &lt;strong&gt;CSS&lt;/strong&gt; and &lt;strong&gt;Tailwind CSS&lt;/strong&gt; plugins.&lt;/p&gt;

&lt;p&gt;That’s it! WebStorm will now provide autocomplete for Tailwind classes in your markup! Additionally, Webstorm will also allow you to preview the resulting CSS if you hover over a Tailwind class, and the IDE can provide code completion based on the customization made through the &lt;code&gt;tailwind.config.js&lt;/code&gt; file (if you add new colors, or extend the default typography settings, for example). &lt;/p&gt;

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

&lt;p&gt;Tailwind makes writing CSS a breeze. It’s lightning quick! But with the aid of the Tailwind CSS VSCode autocomplete feature, not only is it faster to find utility classes you want to add to your markup, but you also get the added benefit of a plugin that bundles linting for real-time error identification and hover previews for when you want to glance at the code beneath the class name. Furthermore, you can have peace of mind knowing that the plugin will never become stale or outdated because the Tailwind CSS team are behind the extension and will continue to push updates as new features are released!&lt;/p&gt;

</description>
      <category>tailwindcss</category>
      <category>webdev</category>
      <category>css</category>
    </item>
    <item>
      <title>What Makes a Unit Test Valuable?</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Mon, 25 Apr 2022 14:16:07 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/what-makes-a-unit-test-valuable-5fpi</link>
      <guid>https://dev.to/braydoncoyer/what-makes-a-unit-test-valuable-5fpi</guid>
      <description>&lt;p&gt;The goal of unit testing is to ensure that your application behaves as expected. However, many unit tests miss the mark, neglecting to provide value to the business and failing to discover defects in the codebase. &lt;/p&gt;

&lt;p&gt;In this article, we’ll discover how to craft unit tests that are valuable and catch defects quickly. But first, let’s look at a definition of a unit test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a unit test?
&lt;/h2&gt;

&lt;p&gt;A unit test is an automated piece of code that invokes a unit of work (a separate piece of code) in the application. The test passes or fails based on an assumption about the behavior of that unit of work (we call this the &lt;strong&gt;&lt;em&gt;code under test&lt;/em&gt;&lt;/strong&gt;). A unit test is almost always written using a unit testing framework, allowing tests to be written efficiently and run quickly.&lt;/p&gt;

&lt;p&gt;If you're just getting started with unit testing, one of the most important steps you can take is understanding the value that unit testing provides, and making sure that you have enough tests in place to keep your application stable. Whether you're writing a single component test or a suite of unit tests for an entire module, it's important for your code to be tested thoroughly and in isolation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📢 If you want to learn more about testing in isolation, &lt;a href="https://braydoncoyer.dev/blog/mocking-components-in-angular#testing-in-isolation"&gt;I wrote another article&lt;/a&gt; that may prove helpful.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unit tests are most valuable when they are easy to read, test a single outcome, share the same structure, aren’t concerned about underlying logic, and all unit tests are repeatable. &lt;/p&gt;

&lt;h2&gt;
  
  
  Unit Tests Should be Easy to Read (Unit Testing Naming Conventions)
&lt;/h2&gt;

&lt;p&gt;The naming convention of your unit tests is also important because it can help make the test’s purpose clear to new developers (this is especially useful for large teams where there are many developers contributing to a codebase). Furthermore, the more specific the test name, the easier it is to identify what went wrong when a test fails.&lt;/p&gt;

&lt;p&gt;For example, imagine you have a function that uses geolocation services to return the longitude and latitude of the user’s current location. Instead of creating a test and calling it &lt;code&gt;getCurrentLocation()&lt;/code&gt; or &lt;code&gt;getLocation()&lt;/code&gt;, be a little more verbose and name the method &lt;code&gt;returnsLongAndLatOfUser()&lt;/code&gt;. This name describes what the test is actually doing: checking to see if the function returns the expected location of the user.&lt;/p&gt;

&lt;p&gt;Some testing frameworks have their own convention for naming your unit tests. Test functions in Jasmine, an open-source frontend testing framework, are declared as &lt;code&gt;it()&lt;/code&gt;, and accept a parameter for the name of the test. This provides a suite of tests that can be read in plain English.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;returns the longitude and latitude of the current user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Test code here&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 This test could, arguably, be split into two separate tests - one that checks that the function returns the &lt;em&gt;longitude&lt;/em&gt; and one that checks that the function returns the &lt;em&gt;latitude&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If this test were to fail, it would be very easy to understand what’s going wrong in your application. &lt;/p&gt;

&lt;h2&gt;
  
  
  Unit Tests Should Only Test One Outcome
&lt;/h2&gt;

&lt;p&gt;This is a common mistake I see in many test suites. It can be tempting to write a catch-all test scenario where you call an implementation function and make multiple expectations in a single unit test. Tests that have multiple assertions and fail make it difficult to understand why the test is failing. And as you can expect, testing multiple outcomes in a single unit test usually have vague test names. &lt;/p&gt;

&lt;p&gt;Valuable Unit Tests should be granular and only make a single assertion. &lt;/p&gt;

&lt;h2&gt;
  
  
  Unit Tests Should Share the Same Structure
&lt;/h2&gt;

&lt;p&gt;Ultimately, how you structure your unit tests is up to you and the team you are working with. Take the time to research patterns and conventions put into place by larger, more seasoned teams. Work with what makes sense for your team, and always keep an eye out for improvements that could be made.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unit Tests Shouldn’t be Concerned About the Underlying Logic
&lt;/h2&gt;

&lt;p&gt;You must be very careful when writing unit tests to ensure that you do not couple the test to the implementation logic. If your test fails due to a change in implementation while the behavior remains the application remains the same, it can be an indicator that you may need to re-examine your test.&lt;/p&gt;

&lt;p&gt;Valuable tests should only be concerned with the expected behavior of a class or method, not how that behavior is implemented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unit Tests Should be Thorough
&lt;/h2&gt;

&lt;p&gt;Thorough is a tricky word and can mean different things to different people. What's important is that you take the time to consider your code through different lenses and discover paths that may have been missed (if statements determine different logical routes; ensure your tests check each scenario!).&lt;/p&gt;

&lt;h2&gt;
  
  
  A Unit Test Should be Repeatable
&lt;/h2&gt;

&lt;p&gt;Finally, valuable unit tests are repeatable. If you run the same test ten times in a row, you should expect to get the same result each time.&lt;/p&gt;

&lt;p&gt;If your unit tests fail this simple check, then something may be wrong with how your test is set up. &lt;/p&gt;

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

&lt;p&gt;In this article, we looked at the characteristics of valuable unit tests. These points aid in the correctness of the code under test which saves time for the developers and saves the business a significant amount of money that would otherwise be spent on hours of debugging. Unit testing provides implicit code documentation and can be a great way for new developers to understand what’s happening in the codebase. Unit tests are extremely valuable - make sure you take the time to craft valuable tests.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>unittesting</category>
    </item>
    <item>
      <title>You Don't Need a CS Degree to Land a Web Development Job</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Tue, 19 Apr 2022 19:23:53 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/you-dont-need-a-cs-degree-to-land-a-web-development-job-1c5n</link>
      <guid>https://dev.to/braydoncoyer/you-dont-need-a-cs-degree-to-land-a-web-development-job-1c5n</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;a href="https://braydoncoyer.dev/blog/you-don%27t-need-a-cs-degree-to-land-a-web-development-job"&gt;This article was originally published on my personal blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Demand is high as many set their sights on a career in web development. &lt;/p&gt;

&lt;p&gt;One of the most appealing aspects of learning web development is the incredible versatility of this sphere of programming. People just starting out can choose between front-end or back-end development—or even full-stack engineering! If you're interested in any field that touches on technology or design, learning to code is a terrific way to build foundational knowledge that will help you succeed in any occupation.&lt;/p&gt;

&lt;p&gt;Whether you’re looking to start your career in web development or switching from a different type of work altogether, the question of education and experience has no doubt crossed your mind.&lt;/p&gt;

&lt;p&gt;Do you &lt;strong&gt;&lt;em&gt;really&lt;/em&gt;&lt;/strong&gt; need a Computer Science (CS) degree to land a job in Web Development? Here’s my argument on why I think the answer is no (spoiler: I don’t have a CS degree). &lt;/p&gt;

&lt;h2&gt;
  
  
  Degrees Aren't as Important as They Once Were
&lt;/h2&gt;

&lt;p&gt;I’ve interviewed over 150 candidates for entry and senior level web development positions and at the end of the day, the decision to extend an offer wasn’t dependent on their level of education.&lt;/p&gt;

&lt;p&gt;The reality is that a formal degree isn't as important as it once was. Most employers aren't looking to fill a position because you have the piece of paper. Rather, they're looking for someone who can do the job and achieve results to benefit their business. Experience is becoming more important than education.&lt;/p&gt;

&lt;p&gt;In fact, computer science degrees are not required for any of the work done at FAANG companies like Google. Only 15% of Google's employees worldwide have a CS degree, according to Laszlo Bock, the company's senior vice president for people operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  You Can Learn to Code Online
&lt;/h2&gt;

&lt;p&gt;If you're looking to break into web development, there are plenty of opportunities to enhance your skills and gain experience outside of college, including many free online resources. Code Academy, FreeCodeCamp, Khan Academy, Egghead and online course providers like Udemy and Pluralsight are only a few examples of the wide range of helpful resources available.&lt;/p&gt;

&lt;p&gt;Being an online learner also allows you to learn at your own pace. Have a full-time job? No problem - study a little bit in the evening after you get home. Only have time to dedicate time on the weekends? Not to worry - there’s no pressure on cramming or meeting deadlines. The pace at which you learn is up to you and your unique situation.&lt;/p&gt;

&lt;p&gt;If you have more money than time (or if you'd prefer to learn from a teacher), there are many different paid courses and bootcamps designed specifically for those learning how to code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Development Bootcamps Provide More Real-world Experience
&lt;/h2&gt;

&lt;p&gt;In my experience, graduates from bootcamps are often far more prepared than those with traditional computer science degrees. Bootcamp grads are able to hit the ground running and dive into projects because of the real-world experience and training they receive in the program.&lt;/p&gt;

&lt;p&gt;In fact, many college curriculums are extremely outdated and fail to teach students the skills that they need to succeed in most real-world jobs.&lt;/p&gt;

&lt;p&gt;Bootcamp graduates also often have more technical experience than those with a relevant degree, as compared to a college program where students spend a lot of time learning theoretical concepts and not necessarily applying them to real-life situations.&lt;/p&gt;

&lt;p&gt;Bootcamps are designed to force students to spend time programming and solving problems that they would face in an actual development position.&lt;/p&gt;

&lt;p&gt;It’s important to note, though, that coding bootcamps usually aren’t free. Like a traditional college degree, many bootcamps require some form of compensation for the training students undergo. However, it’s extremely uncommon for the expense to be anywhere as high as the daunting student debt that can pile up with with a four-year degree.&lt;/p&gt;

&lt;p&gt;If you’re looking for a structured program to help you land your first web development position, consider looking into bootcamps such as &lt;a href="https://codeup.com/program/data-science/?utm_campaign=The+Social+Robin+-+Search+-+San+Antonio+-+DS&amp;amp;utm_medium=ppc&amp;amp;utm_term=codeup&amp;amp;utm_source=adwords&amp;amp;hsa_net=adwords&amp;amp;hsa_mt=b&amp;amp;hsa_ver=3&amp;amp;hsa_grp=101818371279&amp;amp;hsa_ad=452789059416&amp;amp;hsa_tgt=kwd-303629395623&amp;amp;hsa_cam=10300080163&amp;amp;hsa_acc=2267998492&amp;amp;hsa_src=g&amp;amp;hsa_kw=codeup&amp;amp;gclid=Cj0KCQjwxtSSBhDYARIsAEn0thRnWvMaKbE_dUrmNu4kIUmJwg1d74CiAd7GCN0d_LAvZyiQXTxnYUYaAtHTEALw_wcB"&gt;Code Up&lt;/a&gt;, &lt;a href="https://flatironschool.com/welcome-to-flatiron-school/?utm_source=Google&amp;amp;utm_medium=ppc&amp;amp;utm_campaign=12728169839&amp;amp;utm_content=127574231184&amp;amp;utm_term=flatiron%20school&amp;amp;uqaid=513799628798&amp;amp;Cj0KCQjwxtSSBhDYARIsAEn0thTQa0u-zcWd68JDoXAiiWlpyLCj_eTlXehvrKWzRHUbRPMJBI-jwvcaAq_SEALw_wcB&amp;amp;gclid=Cj0KCQjwxtSSBhDYARIsAEn0thTQa0u-zcWd68JDoXAiiWlpyLCj_eTlXehvrKWzRHUbRPMJBI-jwvcaAq_SEALw_wcB"&gt;Flatiron School&lt;/a&gt;, &lt;a href="https://www.udacity.com/online-learning-for-individuals?utm_source=gsem_brand&amp;amp;utm_medium=ads_r&amp;amp;utm_campaign=747168232_c_individuals&amp;amp;utm_term=126315200811&amp;amp;utm_keyword=udacity_e&amp;amp;gclid=Cj0KCQjwxtSSBhDYARIsAEn0thSld1mtcnAqhPGWjTX4vEhFdqQZkDqM_jExREc5iNkyu3FnYb5HL9AaAsgrEALw_wcB"&gt;Udacity&lt;/a&gt; and &lt;a href="https://www.codingdojo.com/"&gt;Coding Dojo&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  What if I decide to get a Computer Science Degree?
&lt;/h2&gt;

&lt;p&gt;I’m not saying that going to school and earning a 4-year degree isn’t worth it. On the contrary, if you decide that a degree is the best route for you to take, then &lt;em&gt;it is&lt;/em&gt; worth it! You’ll learn valuable skills and you’ll still be able to land a job! &lt;/p&gt;

&lt;p&gt;The argument I’m extending in this article is rather to provide an alternate viewpoint: that the job landscape has shifted and a degree is no longer &lt;em&gt;required&lt;/em&gt; to land a programming job.&lt;/p&gt;

&lt;p&gt;Granted, this is from my personal experience and I’m sure this may not be the case everywhere in the world. There will be some who disagree with me - and that’s fine! But I’ve seen countless individuals land jobs in tech with as little as 4 months of programming experience. Some create their own path and are self-taught. Others choose to be a little more structured and attend a bootcamp. There is no “right” way to get your foot in the door!&lt;/p&gt;

&lt;h2&gt;
  
  
  The job market is wide open for coding jobs
&lt;/h2&gt;

&lt;p&gt;Landing a web development job in 2022 is more achievable than ever before; the demand for people with coding skills far exceeds the supply.&lt;/p&gt;

&lt;p&gt;Companies across nearly every industry are eager to find someone with coding experience, but they're having trouble finding candidates who meet their standards. Many companies choose to hire junior programmers straight out of high school and train them on the job, or even bring on non-programmers and teach them how to code.&lt;/p&gt;

&lt;p&gt;Now is the time for you to start programming and become a web developer - with so many online resources available, you can even do it without a college degree - I did, and I’ve found great success and fulfilment in this career!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>computerscience</category>
      <category>discuss</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Powerful CSS not Selector</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Tue, 29 Mar 2022 12:56:38 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/the-powerful-css-not-selector-20fg</link>
      <guid>https://dev.to/braydoncoyer/the-powerful-css-not-selector-20fg</guid>
      <description>&lt;p&gt;&lt;a href="https://braydoncoyer.dev/blog/the-powerful-css-not-selector"&gt;This article was originally posted on my personal blog.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;:not()&lt;/code&gt; CSS selector is a powerful addition to the pseudo-class toolbelt, allowing you to select elements that are omitted by its argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  A basic :not() CSS Selector Example
&lt;/h2&gt;

&lt;p&gt;Here’s an example. I have a few classes set up - one applies base styles for all buttons, one sets the styles of a primary button, and another determines what a primary disabled button should look like.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📢 I’m using SCSS in the example below to gain the benefit of class nesting and variables, but the application of the &lt;code&gt;:not()&lt;/code&gt; selector is the same.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.button--primary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$button--primary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.button--disabled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$button--disabled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/braydoncoyer/embed/KKZmNJa?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In order to align with accessibility, it’s important that the background of the button changes when in &lt;code&gt;hover&lt;/code&gt; state. That’s simple enough; here’s the change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.button--primary&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$button-primary-hover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, after adding the &lt;code&gt;:hover&lt;/code&gt; selector, we run into a problem. Try hovering over the disabled button and notice that the background changes as if we were hovering over an active primary button.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/braydoncoyer/embed/dyJWOBr?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;How do we get around this? The &lt;code&gt;:not()&lt;/code&gt; selector makes this an easy fix, allowing the change to only affect primary buttons that are &lt;em&gt;not&lt;/em&gt; disabled!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.button--primary&lt;/span&gt;&lt;span class="nd"&gt;:hover:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.button--disabled&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$button-primary-hover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/braydoncoyer/embed/JjMNbgm?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📢 Instead of using a class to determine if the button is disabled, I could have opted to use the &lt;code&gt;:disabled&lt;/code&gt; attribute. I think the examples above are a bit easier to follow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Browser Compatibility for the :not Selector
&lt;/h2&gt;

&lt;p&gt;Thankfully, the &lt;code&gt;:not()&lt;/code&gt; selector is supported by most major browsers. &lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://caniuse.com/css-not-sel-list"&gt;caniuse.com&lt;/a&gt; to see the exceptions.&lt;/p&gt;

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

&lt;p&gt;In this article, we briefly discussed the &lt;code&gt;:not()&lt;/code&gt; selector and saw a real-world example. A variety of options open up when using this selector - what applications can you think of?&lt;/p&gt;

</description>
      <category>css</category>
      <category>selectors</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Enable Preview Mode in Next.js for your CMS</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Tue, 15 Mar 2022 13:37:51 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/how-to-enable-preview-mode-in-nextjs-for-your-cms-1f7j</link>
      <guid>https://dev.to/braydoncoyer/how-to-enable-preview-mode-in-nextjs-for-your-cms-1f7j</guid>
      <description>&lt;p&gt;I’m using Notion as a CMS by utilizing the Notion API (which was just released to the general public) for my website and blog. There are a lot of benefits to using Notion as a CMS and if you’d like to read more, &lt;a href="https://braydoncoyer.dev/blog/introducing-my-new-blogfolio#notion-as-a-cms"&gt;I wrote a little blurb about it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Writing articles in Notion is great and all, but I didn’t have a way to preview what the article would look like on my blog before publishing. I would cross my fingers and hope that everything worked on my live site. But, as you may suspect, there were times when I didn’t catch small issues before I published the article and it wasn’t uncommon for people to message me on Twitter to inform me that something wasn’t right.&lt;/p&gt;

&lt;p&gt;There had to be a way that I could preview unpublished content without affecting visitors to my site. Turns out, Next.js provides a very simple but powerful solution. It’s called Preview Mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Next.js Preview Mode?
&lt;/h2&gt;

&lt;p&gt;Preview Mode renders pages at request time at a specific URL so you can preview draft content without having to worry that visitors to your website will see your unfinished work. This was specifically created for headless Content Management Systems like Strapi, Contentful, Sanity.io and Notion - though the implementation is CMS agnostic. &lt;/p&gt;

&lt;p&gt;Let’s take a look at how the Next.js Preview Mode works.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Preview Mode work?
&lt;/h3&gt;

&lt;p&gt;Next.js needs to understand that we’re trying to preview the site and any unpublished content. In order to do that, Next.js created a special function that sets specific cookies in our browser and turns on the Preview Mode. &lt;/p&gt;

&lt;p&gt;Because those cookies were set, any incoming requests to the browser with these cookies will allow us to make quick checks in our logic to filter out draft content if the visitor is not browsing in Preview Mode. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📢 Please note that Preview Mode is only available for Next.js versions 9.3 and up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Make sense? Let’s see it in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use the Next.js Preview Mode
&lt;/h2&gt;

&lt;p&gt;Remember, we first need to inform Next.js that we want to view our site in preview mode. To do that, create a new API Route. The name of the API Route doesn’t matter, but for the sake of this tutorial let’s call it &lt;code&gt;pages/api/preview.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next.js provides a special function that we can call which will set cookies in our browser. Inside the API Route, call the &lt;code&gt;setPreviewData&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setPreviewData&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s literally all you have to do to enable Preview Mode in your Next.js application. &lt;/p&gt;

&lt;p&gt;But let’s take it a step further. Let’s pass in a query parameter so we can redirect to a specific page once the cookies have been set. &lt;/p&gt;

&lt;p&gt;Thankfully, Next.js makes this extremely easy. Simply call the &lt;code&gt;redirect&lt;/code&gt; function on the &lt;code&gt;res&lt;/code&gt; object and pass in the appropriate value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setPreviewData&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Display Draft Content with Preview Mode Enabled
&lt;/h2&gt;

&lt;p&gt;Now that Preview Mode is active, we need to add some logic to our application that will allow unpublished content to be displayed on our website. &lt;/p&gt;

&lt;p&gt;You may have a certain flag in your content object that identifies if it's published or not - since I’m using Notion as a CMS, I have an &lt;code&gt;isPublic&lt;/code&gt; boolean on each of my articles that controls whether or not the article is available for visitors to read.&lt;/p&gt;

&lt;p&gt;I’m fetching this data in &lt;code&gt;getStaticProps&lt;/code&gt; method so I can take advantage of static generation. If you’re doing the same thing, destructure the new &lt;code&gt;preview&lt;/code&gt; prop and use it to filter content based on its boolean value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStaticProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getAllArticles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPublic&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice our conditional check - if Preview Mode is disabled, we only want to return articles that &lt;em&gt;have been published&lt;/em&gt;. Otherwise, we skip that block and return all of the content. &lt;/p&gt;

&lt;p&gt;Nifty - and super simple to use! &lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out!
&lt;/h2&gt;

&lt;p&gt;Now that everything is set up, let’s give it a shot!&lt;/p&gt;

&lt;p&gt;Try navigating to &lt;code&gt;www.yoursitename.com/api/preview?redirect=/blog&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You should be automatically redirected to your intended path and you should see your draft content as well!&lt;/p&gt;

&lt;p&gt;If you open the storage tab in your browser, you’ll see the Preview Mode cookies that Next.js set when we hit that special URL!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9qwzC4Dr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1647100336/Screen_Shot_2022-03-12_at_9.51.35_AM_tbuabf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9qwzC4Dr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1647100336/Screen_Shot_2022-03-12_at_9.51.35_AM_tbuabf.png" alt="Preview Mode cookies that were set when we hit the API Route" width="880" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I feel much more confident writing articles for my blog with Preview Mode enabled; I finally have a way to check the content before I hit the publish button!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Tailwind Gradients - How to Make a Glowing Gradient Background</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Thu, 10 Feb 2022 00:18:54 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/tailwind-gradients-how-to-make-a-glowing-gradient-background-1cab</link>
      <guid>https://dev.to/braydoncoyer/tailwind-gradients-how-to-make-a-glowing-gradient-background-1cab</guid>
      <description>&lt;p&gt;This article was originally published on &lt;a href="https://braydoncoyer.dev/blog/tailwind-gradients-how-to-make-a-glowing-gradient-background" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Gradients have become extremely popular on the web. In fact, it’s unusual to see a product website without a gradient to add a splash of color and contrast all of the negative space. &lt;/p&gt;

&lt;p&gt;Subtlety can go a long way - and in this brief article, I’ll show you how to achieve a powerful glowing gradient effect with Tailwind. &lt;/p&gt;

&lt;p&gt;Here’s what we’ll be building:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/twhite96/embed/LYzwoLm?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailwind CSS Gradients
&lt;/h2&gt;

&lt;p&gt;Before we dive into the implementation, it’s important to understand how gradients work in Tailwind. Tailwind added support for gradients in its second major release, providing several new utility classes to add color stops to achieve a gradient background on an element.&lt;/p&gt;

&lt;p&gt;In a very basic linear example, you must define the starting color and the ending color, using the &lt;code&gt;from-{color}&lt;/code&gt; and &lt;code&gt;to-{color}&lt;/code&gt; utilities respectively.  Here’s an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"h-14 bg-gradient-to-r from-cyan-500 to-blue-500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69ue76do14zbx5kp00pf.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69ue76do14zbx5kp00pf.png" alt="basic Tailwind gradient"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the gradient direction
&lt;/h3&gt;

&lt;p&gt;You may have noticed an extra utility class in the example above (&lt;code&gt;bg-gradient-to-r&lt;/code&gt;). In addition to the color stops, it’s important to define the direction of the linear gradient on the targeted element. &lt;/p&gt;

&lt;p&gt;There are several utility classes available to define the Tailwind gradient direction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bg-gradient-to-t&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg-gradient-to-tr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg-gradient-to-r&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg-gradient-to-br&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg-gradient-to-b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg-gradient-to-bl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg-gradient-to-l&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg-gradient-to-tl&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s also worth noting that you can apply the direction and color of the gradient conditionally. &lt;/p&gt;

&lt;p&gt;For example, if you wanted to change the direction of the gradient on element hover, you may use the following utility classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-gradient-to-r hover:bg-gradient-to-l from-purple-500 to-pink-500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tailwind Gradients with more than two color stops
&lt;/h3&gt;

&lt;p&gt;It’s possible to add an additional color stop to Tailwind linear gradients. You can achieve this using the &lt;code&gt;via-{color}&lt;/code&gt; utilities. Elements with this utility applied will fade seamlessly between the first, second and ending color stops.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"h-24 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawd12j0rjrpm4ckdh0n6.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawd12j0rjrpm4ckdh0n6.png" alt="gradient with more than two steps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Your Environment to Create a Glowing Gradient
&lt;/h2&gt;

&lt;p&gt;Now that we have a basic understanding of how Tailwind handles gradients, we can turn our attention to the implementation. If you’re wanting to add this glowing gradient effect in a side-project, chances are you have a React, Vue or Angular repository already spun up and standing by. If you haven’t installed Tailwind in your framework of choice, check out the &lt;a href="https://tailwindcss.com/docs/installation/framework-guides" rel="noopener noreferrer"&gt;official spin-up guides here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re just tinkering and want to try new things, the Tailwind team has their own web-based editor with the library configured and ready-to-go. I recommend trying &lt;a href="https://play.tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind Play&lt;/a&gt; and following along with this tutorial. &lt;a href="https://codepen.io/trending" rel="noopener noreferrer"&gt;CodePen&lt;/a&gt; is another great option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Background and Constraints with Tailwind CSS
&lt;/h2&gt;

&lt;p&gt;Before we start working on the card and glowing gradient, let’s create the background and add in some width constraints so that the card doesn’t take up the entire screen. You can tweak this to your needs. The code below adds a subtle gray background, uses Flexbox to center the child &lt;code&gt;div&lt;/code&gt; and applies a max-width.&lt;/p&gt;

&lt;p&gt;For future steps, all markup will be wrapped inside these two container &lt;code&gt;div&lt;/code&gt; elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"min-h-screen bg-gray-50 flex flex-col justify-center relative overflow-hidden sm:py-12"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"max-w-7xl mx-auto"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- All markup will go here --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the Card with Basic Utility Classes
&lt;/h2&gt;

&lt;p&gt;Next, create a &lt;code&gt;div&lt;/code&gt; element representing the card itself. Apply some &lt;em&gt;X&lt;/em&gt; and &lt;em&gt;Y&lt;/em&gt; padding, make the background white, round the corners and apply some Flexbox utility classes for what’s to come.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"relative px-7 py-6 bg-white ring-1 ring-gray-900/5 rounded-lg leading-none flex items-top justify-start space-x-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Card Content
&lt;/h3&gt;

&lt;p&gt;Now that the container of the card has been established, we can fill it out with some content. The example I’ve created uses an icon on the left, and the card information on the right.&lt;/p&gt;

&lt;p&gt;Give the icon a size and optionally add a color using the &lt;code&gt;text-{color}&lt;/code&gt; utility class. &lt;/p&gt;

&lt;p&gt;The actual text content of the card is placed within a containing &lt;code&gt;div&lt;/code&gt; , breaking free from the parent’s flex layout and changing it back to the default display value of &lt;code&gt;block&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The containing &lt;code&gt;div&lt;/code&gt; has a &lt;code&gt;space-y-2&lt;/code&gt; utility applied to it which will add margin between the child elements, providing some subtle negative space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"relative px-7 py-6 bg-white ring-1 ring-gray-900/5 rounded-lg leading-none flex items-top justify-start space-x-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- card content --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-8 h-8 text-purple-600"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;stroke-linecap=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-linejoin=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"1.5"&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M6.75 6.75C6.75 5.64543 7.64543 4.75 8.75 4.75H15.25C16.3546 4.75 17.25 5.64543 17.25 6.75V19.25L12 14.75L6.75 19.25V6.75Z"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/path&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-slate-800"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Learn how to make a glowing gradient background!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block text-indigo-400 group-hover:text-slate-800 transition duration-200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Read Article →&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;👉 NOTE: The card container does not need to be a flex element if you don’t want content side-by-side.&lt;/p&gt;
&lt;/blockquote&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ac44c40uim5106v16ze.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ac44c40uim5106v16ze.png" alt="The card with content"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With all this in place, we’re ready to add the gradient! &lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Glowing Gradient with Tailwind Utility Classes
&lt;/h2&gt;

&lt;p&gt;Making the glowing gradient is shockingly simple. In fact, we really only need one additional element. &lt;/p&gt;

&lt;p&gt;Inside of the first container &lt;code&gt;div&lt;/code&gt; used to apply a max-width, create an additional &lt;code&gt;div&lt;/code&gt; at the same hierarchy level as the &lt;code&gt;div&lt;/code&gt; used for the card. Apply the following class names to the new element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"min-h-screen bg-gray-50 flex flex-col justify-center relative overflow-hidden sm:py-12"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"max-w-7xl mx-auto"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="c"&gt;&amp;lt;!-- New Div --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"absolute -inset-1 bg-gradient-to-r from-purple-600 to-pink-600 rounded-lg blur opacity-25"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"relative px-7 py-6 bg-white ring-1 ring-gray-900/5 rounded-lg leading-none flex items-top justify-start space-x-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-8 h-8 text-purple-600"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;stroke-linecap=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-linejoin=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"1.5"&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M6.75 6.75C6.75 5.64543 7.64543 4.75 8.75 4.75H15.25C16.3546 4.75 17.25 5.64543 17.25 6.75V19.25L12 14.75L6.75 19.25V6.75Z"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/path&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-slate-800"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Learn how to make a glowing gradient background!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block text-indigo-400 group-hover:text-slate-800 transition duration-200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Read Article →&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic happens with the &lt;code&gt;-inset-1&lt;/code&gt; utility class that Tailwind provides, anchoring absolutely positioned elements against the edges of the nearest positioned parent. Since the &lt;code&gt;inset&lt;/code&gt; utility class is prefixed with a negative value, the element will be positioned outside of the top, right, left and bottom of the parent element.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk8ee70smhua8c4h66hdx.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk8ee70smhua8c4h66hdx.png" alt="The gradient background isn't applied to the card"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, since we haven’t set a reference point for this element, it doesn’t give us an expected result. To fix this, add the &lt;code&gt;relative&lt;/code&gt; class to the parent &lt;code&gt;div&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"relative max-w-7xl mx-auto"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"absolute -inset-1 bg-gradient-to-r from-purple-600 to-pink-600 rounded-lg blur opacity-25"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"relative px-7 py-6 bg-white ring-1 ring-gray-900/5 rounded-lg leading-none flex items-top justify-start space-x-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-8 h-8 text-purple-600"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;stroke-linecap=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-linejoin=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"1.5"&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M6.75 6.75C6.75 5.64543 7.64543 4.75 8.75 4.75H15.25C16.3546 4.75 17.25 5.64543 17.25 6.75V19.25L12 14.75L6.75 19.25V6.75Z"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/path&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-slate-800"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Learn how to make a glowing gradient background!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block text-indigo-400 group-hover:text-slate-800 transition duration-200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Read Article →&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw0l11jdbtpf1dzmzerr.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw0l11jdbtpf1dzmzerr.png" alt="The gradient background has been fixed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to tweak the gradient colors, gradient direction, blur and opacity to your liking!&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing the Gradient Hover State in Tailwind with the Group Utility and Hover Modifier
&lt;/h3&gt;

&lt;p&gt;In its current state, the gradient is noticeable behind the card but it may be nice to increase the opacity of the gradient when the user hovers over the card. Thankfully, Tailwind makes this an easy task with the help of the &lt;code&gt;hover&lt;/code&gt; modifier. &lt;/p&gt;

&lt;p&gt;Traditionally, the &lt;code&gt;hover&lt;/code&gt; modifier activates when your mouse overlaps with the element. However, what if we want our gradient to change when the mouse is over any part of the card, not just the gradient? &lt;/p&gt;

&lt;p&gt;This is where the &lt;code&gt;group&lt;/code&gt; utility class comes into play. The Tailwind &lt;code&gt;group&lt;/code&gt; utility class allows styles to be applied based on the state of some parent element. The target element can change with the &lt;code&gt;group-{modifier}&lt;/code&gt; utility. &lt;/p&gt;

&lt;p&gt;In our example, we want to apply the &lt;code&gt;group&lt;/code&gt; class to the parent card &lt;code&gt;div&lt;/code&gt;, and then set &lt;code&gt;group-hover:opacity-100&lt;/code&gt; modifier on the gradient itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"group relative max-w-7xl mx-auto"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"absolute -inset-1 bg-gradient-to-r from-purple-600 to-pink-600 rounded-lg blur opacity-25 group-hover:opacity-100 transition duration-1000 group-hover:duration-200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-7 py-6 bg-white ring-1 ring-gray-900/5 rounded-lg leading-none flex items-top justify-start space-x-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-8 h-8 text-purple-600"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;stroke-linecap=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-linejoin=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"1.5"&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M6.75 6.75C6.75 5.64543 7.64543 4.75 8.75 4.75H15.25C16.3546 4.75 17.25 5.64543 17.25 6.75V19.25L12 14.75L6.75 19.25V6.75Z"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/path&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-slate-800"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Learn how to make a glowing gradient background!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block text-indigo-400 group-hover:text-slate-800 transition duration-200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Read Article →&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, whenever the mouse is over any part of the card, the gradient’s opacity will increase to 100%. The effect is a bit jarring, though - let’s add a nice transition to allow the opacity to gradually increase and decrease.&lt;/p&gt;

&lt;p&gt;To do this, simply add &lt;code&gt;transition duration-1000&lt;/code&gt; to the gradient &lt;code&gt;div&lt;/code&gt; signaling Tailwind to make the transition 1 second long. Optionally, if you wanted the increase in opacity to be quicker, you can add the &lt;code&gt;group-hover:duration-200&lt;/code&gt; utility class to the &lt;code&gt;div&lt;/code&gt; as well. &lt;/p&gt;

&lt;p&gt;With that we now have replicated the popular glowing gradient effect with Tailwind CSS! &lt;/p&gt;

&lt;p&gt;Here’s the final result! &lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/twhite96/embed/LYzwoLm?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  A Word on Safari Support
&lt;/h2&gt;

&lt;p&gt;If you test your changes in Safari, the result may look a bit strange and unexpected. Because of key support missing in Safari, you may need to ensure &lt;a href="https://tailwindcss.com/docs/using-with-preprocessors#using-post-css-as-your-preprocessor" rel="noopener noreferrer"&gt;Autoprefixer is installed and configured in your project&lt;/a&gt;. &lt;/p&gt;

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

&lt;p&gt;Thanks for reading! If you enjoyed this article, consider signing up for &lt;a href="https://www.getrevue.co/profile/braydoncoyer" rel="noopener noreferrer"&gt;my newsletter&lt;/a&gt; so you don't miss out on helpful content like this!&lt;/p&gt;

</description>
      <category>tailwindcss</category>
      <category>gradients</category>
      <category>webdev</category>
      <category>css</category>
    </item>
    <item>
      <title>6 BEST Fonts for Programming in 2022</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Wed, 26 Jan 2022 14:34:11 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/6-best-fonts-for-programming-in-2022-4n35</link>
      <guid>https://dev.to/braydoncoyer/6-best-fonts-for-programming-in-2022-4n35</guid>
      <description>&lt;p&gt;If you’re looking for a fresh font at the beginning of the year, this list is sure to help you discover a few new favorites!&lt;/p&gt;

&lt;p&gt;Monospaced fonts are often considered ideal for programmers, as all characters occupy the exact same width making it easy to check, align or compare text.&lt;/p&gt;

&lt;h2&gt;
  
  
  1: MonoLisa
&lt;/h2&gt;

&lt;p&gt;Remaining in the #1 slot from last year, MonoLisa features a few unique techniques that increase the legibility and make it visually pleasant to look at for longer periods of time. &lt;/p&gt;

&lt;p&gt;While MonoLisa is a premium font, it no doubt sticks out as unique even with a quick glance.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://www.monolisa.dev"&gt;MonoLisa here!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--la_s2BSw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/monolisa-01_jpbvaa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--la_s2BSw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/monolisa-01_jpbvaa.png" alt="https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/monolisa-01_jpbvaa.png" width="880" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2: Consolas
&lt;/h2&gt;

&lt;p&gt;A font released by Microsoft and available by default with each Windows installation, Consolas provides an elegant monospaced typeface that many developers choose to use as their daily driver.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://docs.microsoft.com/en-us/typography/font-list/consolas"&gt;Consolas here!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_DjEExDT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/consolas-01_fz61lo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_DjEExDT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/consolas-01_fz61lo.png" alt="https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/consolas-01_fz61lo.png" width="880" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3: Input Mono
&lt;/h2&gt;

&lt;p&gt;Input is a typeface system that allows for many customizations. Crafted by &lt;a href="https://djr.com"&gt;David Jonathan Ross&lt;/a&gt;, Input offers monospaced and proportional fonts.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://input.djr.com"&gt;Input here!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iast3Vkz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/inputMono-01_vqlfvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iast3Vkz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/inputMono-01_vqlfvs.png" alt="https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/inputMono-01_vqlfvs.png" width="880" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4: Monofur
&lt;/h2&gt;

&lt;p&gt;Monofur, an elegant and free typeface designed by Tobias Benjamin Köhler, is a monospace font that isn’t as widely known as it should be.&lt;/p&gt;

&lt;p&gt;Each character is crafted with specific meaning and provides an easy-on-the-eyes option for programmers who spend a lot of time in front of the computer screen.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://www.dafont.com/monofur.font"&gt;Monofur here!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k1N4YEkh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330993/monofur-01_zhqjs0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k1N4YEkh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330993/monofur-01_zhqjs0.png" alt="https://res.cloudinary.com/braydoncoyer/image/upload/v1641330993/monofur-01_zhqjs0.png" width="880" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5: Source Code Pro
&lt;/h2&gt;

&lt;p&gt;A companion to Source Sans, this monospace font was designed by Paul D. Hunt specifically for coding environments.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://fonts.google.com/specimen/Source+Code+Pro#standard-styles"&gt;Source Code Pro here!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DzhoUdgs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330993/sourcecodepro-01_dksdc8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DzhoUdgs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330993/sourcecodepro-01_dksdc8.png" alt="https://res.cloudinary.com/braydoncoyer/image/upload/v1641330993/sourcecodepro-01_dksdc8.png" width="880" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6: DejaVu Sans Mono
&lt;/h2&gt;

&lt;p&gt;An unknown font to me until recently but now one of my favorites, DejaVu provides a beautiful typeface and elegant reading experience for those wanting something a bit more premium without having to pull out the wallet.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://dejavu-fonts.github.io"&gt;DejaVu Sans Mono here!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lBAJ6xZC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/dejavu-01_k74dbm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lBAJ6xZC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/dejavu-01_k74dbm.png" alt="https://res.cloudinary.com/braydoncoyer/image/upload/v1641330992/dejavu-01_k74dbm.png" width="880" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Whether you’re a new developer looking to find a font style you like, or a seasoned vet looking for a change, I trust that the list above provides a great few font options for 2022!&lt;/p&gt;

&lt;p&gt;Thanks for reading! If you liked this article and want more content like this, read some of my other articles and subscribe to my newsletter below!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🤔 Want to see which fonts I recommended in 2021? &lt;a href="https://braydoncoyer.dev/blog/6-best-fonts-for-programming-in-2021"&gt;Check out the list here!&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Explore more highly legible and beautiful fonts in the links below!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://braydoncoyer.dev/blog/6-best-fonts-for-programming-in-2021"&gt;6 BEST Fonts for Programming in 2021&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/ProgrammingFonts/ProgrammingFonts"&gt;GitHub - ProgrammingFonts - a collection of programming fonts&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fonts</category>
      <category>vscode</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>A New Opportunity at LogicGate</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Mon, 17 Jan 2022 16:24:54 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/a-new-opportunity-at-logicgate-3i28</link>
      <guid>https://dev.to/braydoncoyer/a-new-opportunity-at-logicgate-3i28</guid>
      <description>&lt;p&gt;The last 6 weeks have been filled with new beginnings for many individuals in Tech Twitter. It seems the arrival of the new year has sparked many career transitions, and the same is true of me.&lt;/p&gt;

&lt;p&gt;I’m excited to announce my next career move, but before I dive into that I want to look back on my time at Cognizant.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What I Accomplished at Cognizant&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I transitioned to Cognizant in May of 2019, coming on board as a Senior Full-stack Engineer. Cognizant appealed a lot to me - I had the opportunity to dive deep into a side of web development I hadn’t touched, I worked in a paired programming environment with strict test-driven development workflows, and I even got to work alongside my brother on the same development team!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Going Full-Stack&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Before migrating to Cognizant, most of my experience was in front-end development. I had touched back-end here and there, and while I could make some fundamental endpoints, I didn’t consider myself a back-end developer.&lt;/p&gt;

&lt;p&gt;Working in a full-stack environment meant more than back-end though, I also got to work with security and, one of my personal favorites, DevOps.&lt;/p&gt;

&lt;p&gt;I had the opportunity to work with Java and Spring Boot, .NET, the Netflix OSS stack (before some of the tooling was sunset), microservice architecture, Docker, Jenkins, GCP, K8s, Pivotal Cloud Foundry, and much more. I absorbed a lot of information and new tech, and expanded my skill set.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Leading Component Design&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Shortly after joining Cognizant, I was asked to lead the front-end portion of a team working with a major health-care client. Before rolling on, it was decided that a component library would be best suited to aid and speed up the work required by the front-end team.&lt;/p&gt;

&lt;p&gt;I architected, developed and maintained an Angular-based component library that was published to a private Nexus instance and pulled down into the repositories of the development team.&lt;/p&gt;

&lt;p&gt;This also meant I had the opportunity to work with the client and design team to understand the upcoming work, create POCs and help shape the product solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Internal Bootcamp - Enablement&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cognizant is a huge corporation with hundreds of thousands of employees. Unlike many other consultant companies, each new employee spends the first 4 weeks in an internal bootcamp called Enablement. After completing the work on the component library, I rolled onto the Enablement team to bring my front-end expertise to the program and help teach, train and prepare developers for client delivery.&lt;/p&gt;

&lt;p&gt;The internal bootcamp allows developers to work in a mock-client engagement, picking up user stories, fleshing out features and working on a green-field project.&lt;/p&gt;

&lt;p&gt;Over the course of two years, I helped train 1,000 developers, brought new front- end frameworks into the Cognizant consultant practice, helped define the front-end interview process for the organization, and had the pleasure of working with some amazing colleges.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Why it’s time to move on&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I’m extremely proud of everything that I was able to accomplish at Cognizant.&lt;/p&gt;

&lt;p&gt;I learned a lot. I gave a lot.&lt;/p&gt;

&lt;p&gt;But all good things must come to an end. I felt it was time for me to get a change in scenery - and so, December 31st, 2021 was my last working day at Cognizant.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;My next step - LogicGate&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I’m proud to announce that I’ve joined LogicGate as a Software Engineer II Front End!&lt;/p&gt;

&lt;p&gt;LogicGate is a company that has crafted an agile GRC cloud solution that combines powerful functionality with intuitive design to enhance enterprise GRC programs.&lt;/p&gt;

&lt;p&gt;My first day is January 18th, 2022 and I couldn’t be more excited.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Returning to my Speciality&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The last 2.5 years working full-stack have been fantastic. I’ve gained a suite of skills that will serve me throughout my career, but my passion is front-end development. I’m ecstatic that I get to change gears and go back to specializing in my preferred vertical.&lt;/p&gt;

&lt;p&gt;I’ll be bringing my Angular expertise to LogicGate to help shape the future of their GRC cloud solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Why LogicGate?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I find myself in a privileged situation and at a point in my career where I can be picky about what my future looks like. I interviewed with many companies in Q4 of 2021 and had the opportunity to say “no” to some organizations and “yes” to others. At the end of the day, I was left with a few offers on the table, and ultimately I chose LogicGate.&lt;/p&gt;

&lt;p&gt;I knew I wanted to find an organization that put its employees first, believed in its product, had great career growth potential, and ultimately, aligned with many of my values.&lt;/p&gt;

&lt;p&gt;I could not be more excited about my future at LogicGate, and look forward to starting on January 18th!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>Create a Newsletter Subscription Form with Next.js API Routes and the Twitter Revue API</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Mon, 10 Jan 2022 16:02:15 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/create-a-newsletter-subscription-form-with-nextjs-api-routes-and-the-twitter-revue-api-43hd</link>
      <guid>https://dev.to/braydoncoyer/create-a-newsletter-subscription-form-with-nextjs-api-routes-and-the-twitter-revue-api-43hd</guid>
      <description>&lt;p&gt;Collecting emails and shepherding a trusting newsletter list is one of the best ways to nurture a community and grow your personal brand.&lt;/p&gt;

&lt;p&gt;In this article, I’ll show you how easy it is to create a newsletter sign-up form with Next.js API routes, React hooks and the Revue API. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is Revue?
&lt;/h2&gt;

&lt;p&gt;Revue is a newsletter platform that allows writers to easily send both free and paid newsletter issues. &lt;/p&gt;

&lt;p&gt;Revue was originally a service created by a Dutch startup, but was acquired by Twitter in January of 2021.&lt;/p&gt;

&lt;p&gt;More recently, Twitter announced that creators can embed their Revue newsletter into their profile page, allowing visitors to easily discover and subscribe and driving more web developers to choose Revue as their newsletter service of choice.&lt;/p&gt;

&lt;p&gt;Haven’t created a Revue account yet? Create your &lt;a href="https://www.getrevue.co/users/sign_in"&gt;newsletter here&lt;/a&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  The Revue Newsletter API
&lt;/h3&gt;

&lt;p&gt;One of the benefits of using Revue as a newsletter service is the easy-to-use API. &lt;/p&gt;

&lt;p&gt;In order to make API calls with Revue, head to the &lt;em&gt;Integrations&lt;/em&gt; tab under your profile, collect the API key at the bottom of the page, and store it in an environment variable in your Next.js project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tJZHfPvP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1640384001/revue_api_key.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tJZHfPvP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/braydoncoyer/image/upload/v1640384001/revue_api_key.png" alt="Take your API key and store it in an environment variable" width="880" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take your API key and store it in an environment variable&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ NOTE: Revue just recently changed how their API works. Before you can start to consume the API, you have to verify your account. Contact Revue support for more instructions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Create the Subscribe Next.js API Route
&lt;/h2&gt;

&lt;p&gt;In order to subscribe someone to your newsletter, a function must be created that passes the user’s email to the Twitter Revue API.&lt;/p&gt;

&lt;p&gt;While you could use the Revue API directly in a component, creating an abstraction and using a Next.js API Route allows for greater flexibility and separation of concerns. Plus, if you decide to move away from the Revue newsletter service down the line, the only code you’ll need to update is in the API Route - the function interacting with the Revue API.&lt;/p&gt;

&lt;p&gt;If you’re unfamiliar, an API Route in Next.js is a serverless function that provides a solution to build your own API endpoints.&lt;/p&gt;

&lt;p&gt;Under the &lt;code&gt;pages/api&lt;/code&gt; directory in your Next.js project, create a new file called &lt;code&gt;subscribe.ts&lt;/code&gt; and export a default &lt;code&gt;handler&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📣 I’m using TypeScript for this tutorial, but the logic remains mostly the same if your project is in JavaScript.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, assume that this function is passed an email from the &lt;code&gt;req&lt;/code&gt; object. Destructure the email address from the &lt;code&gt;req&lt;/code&gt; object and make a check to see if the email exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email is required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the email is passed to the function, make an API request to the Revue newsletter API and pass the email address along with the API token as part of the Authorization header.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email is required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.getrevue.co/api/v2/subscribers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REVUE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;double_opt_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📣 I’ve passed an additional property to the request called &lt;code&gt;double_opt_in&lt;/code&gt; and set it to &lt;code&gt;false&lt;/code&gt;, informing Revue to add the subscriber to the list without having to confirm their email.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, parse the result and check to make sure there aren’t any errors before returning the status of &lt;code&gt;201&lt;/code&gt; to the caller. The function in final form should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email is required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.getrevue.co/api/v2/subscribers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REVUE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;double_opt_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the Form Types with Typescript
&lt;/h2&gt;

&lt;p&gt;We can leverage TypeScript to apply a state or mode to the subscription form. &lt;/p&gt;

&lt;p&gt;Create a new enum called &lt;code&gt;Form&lt;/code&gt; and add the following states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Initial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;Error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a new type for the form and utilize the enum that was just created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FormState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a React Hook to Subscribers Users
&lt;/h2&gt;

&lt;p&gt;With the API Route ready-to-go, create a hook called &lt;code&gt;useSubscribeToNewsletter.ts&lt;/code&gt; in your project. While we could put the form subscription logic in the component itself, creating an abstraction allows for reusability throughout your project and simplifies the component itself.&lt;/p&gt;

&lt;p&gt;First, create a slice of state to hold the value of the subscription form and then create a reference to the form. The form can be in one of four modes: &lt;code&gt;Initial&lt;/code&gt;, &lt;code&gt;Loading&lt;/code&gt;, &lt;code&gt;Success&lt;/code&gt;, and &lt;code&gt;Error&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;useSubscribeToNewsletter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setForm&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FormState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Initial&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a &lt;code&gt;subscribe&lt;/code&gt; function inside the hook and call the API Route that was created just a few moments ago.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;setForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/subscribe`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the request has been completed, check to see if there’s an error, otherwise set the value of &lt;code&gt;form&lt;/code&gt; to &lt;code&gt;Form.Success&lt;/code&gt; and clear out the content in the form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;setForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/subscribe`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;setForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;inputEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;setForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Success! You've been added to the list!`&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, expose the various items created in the hook so that they can be accessible in a component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputEl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final hook should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;useSubscribeToNewsletter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setForm&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FormState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Initial&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;setForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/subscribe`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;setForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;inputEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;setForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Success! You've been added to the list!`&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputEl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a Subscribe Component
&lt;/h2&gt;

&lt;p&gt;Create a new component called &lt;code&gt;Subscribe.js&lt;/code&gt;, import the &lt;code&gt;useSubscribeToNewsletter&lt;/code&gt; hook and destructure the &lt;code&gt;form&lt;/code&gt;, &lt;code&gt;subscribe&lt;/code&gt; and &lt;code&gt;inputEl&lt;/code&gt; values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputEl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSubscribeToNewsletter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the appropriate markup to create a signup form. Feel free to tweak the markup below to fit your needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputEl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSubscribeToNewsletter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Updates delivered to your inbox!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        A periodic update about my life, recent blog posts, how-tos, and
        discoveries.
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;No spam - unsubscribe at any time!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
          &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;inputEl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bobloblaw@gmail.com"&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
          &lt;span class="na"&gt;autoComplete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Subscribe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;subscribe&lt;/code&gt; function attached to the &lt;code&gt;onSubmit&lt;/code&gt; event, and the &lt;code&gt;inputEl&lt;/code&gt; reference on the input element. &lt;/p&gt;

&lt;p&gt;The button provides a call to action unless the form is in the process of submitting, in which case it updates to give visual feedback to the user. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👏 Feel free to add the appropriate logic to display an error or provide visual feedback to the user of a successful submission.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;There are three main characters in play for this implementation: the component, the hook, and the Next.js API Route. The component remains simple because the logic is encapsulated in the hook while the Next.js API Route is delegated to contact the Twitter Revue newsletter API and add a subscriber to the list.&lt;/p&gt;

&lt;p&gt;Add some styles to your form and try subscribing!&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;If you found this article helpful, consider signing up for my newsletter below! I often write helpful articles like this one and notify you of new articles each month!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>newsletter</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Dynamically Create Open Graph Images with Cloudinary and Next.js</title>
      <dc:creator>Braydon Coyer</dc:creator>
      <pubDate>Tue, 21 Dec 2021 14:47:42 +0000</pubDate>
      <link>https://dev.to/braydoncoyer/how-to-dynamically-create-open-graph-images-with-cloudinary-and-nextjs-1pn3</link>
      <guid>https://dev.to/braydoncoyer/how-to-dynamically-create-open-graph-images-with-cloudinary-and-nextjs-1pn3</guid>
      <description>&lt;p&gt;Have you wondered how sites like &lt;a href="http://dev.to"&gt;dev.to&lt;/a&gt; create dynamic and engaging social sharing banners on Twitter, LinkedIn and Facebook?&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://braydoncoyer.dev/blog/introducing-my-new-blogfolio" rel="noopener noreferrer"&gt;revamped my blogfolio&lt;/a&gt; this year and knew I didn't want to continue to create banner images for my articles, and manually create Open Graph images for my social outlets. &lt;/p&gt;

&lt;p&gt;I'm extremely happy with the result - now when I share my articles online, my Open Graph images look something like this:&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%2Fres.cloudinary.com%2Fbraydoncoyer%2Fimage%2Fupload%2Fv1640022889%2Fdynamic_og_images_preview_ovwxw9.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%2Fres.cloudinary.com%2Fbraydoncoyer%2Fimage%2Fupload%2Fv1640022889%2Fdynamic_og_images_preview_ovwxw9.png" alt="Open Graph image contains article title, author, domain and article banner as an image underlay aligned on the right"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open Graph image contains article title, author, domain and article banner as an image underlay aligned on the right&lt;/p&gt;

&lt;p&gt;In this article, I'll show you how to leverage the powerful Cloudinary API to create dynamic Open Graph images and banners for your website or blog. &lt;/p&gt;

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

&lt;p&gt;Want to know the secret right away? We'll be passing various variables to the Cloudinary image request URL which will transform a template image and add an article title and banner image.&lt;/p&gt;

&lt;p&gt;Read on to learn how to do this, or check out my &lt;a href="https://github.com/braydoncoyer/braydoncoyer.dev" rel="noopener noreferrer"&gt;open source blogfolio on GitHub&lt;/a&gt; and see how I accomplished this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Open Graph meta tags?
&lt;/h2&gt;

&lt;p&gt;Open Graph meta tags help make your content more clickable, sharable and visible on the web, especially on social media.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;meta&lt;/code&gt; tags are small self-closing tags that inform the web how to display your content. The &lt;a href="https://ogp.me" rel="noopener noreferrer"&gt;Open Graph protocol&lt;/a&gt; is part of Facebook's endeavor to consolidate the various technologies and provide developers a single protocol to adhere to in order to allow content to display more richly on the internet. &lt;/p&gt;

&lt;h2&gt;
  
  
  Sign up for Cloudinary
&lt;/h2&gt;

&lt;p&gt;First, head to &lt;a href="https://cloudinary.com/invites/lpov9zyyucivvxsnalc5/gle5rlywpxclhxkdtqur" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt; and create an account.&lt;/p&gt;

&lt;p&gt;Cloudinary has a free tier containing 25 monthly credits, which can be consumed by transforming images, storing images and videos, and spending the bandwidth needed to access assets in your bucket.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✨ Cloudinary gifts you extra monthly credits if you follow their social accounts and spread the word about the service through a message on your timeline. Look for these options on your Dashboard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Upload your OG template to Cloudinary
&lt;/h2&gt;

&lt;p&gt;Next, either find or create a template image that will be used as a starting point for all of the Open Graph banners. This takes care of a lot of initial layout positioning and creates consistency for the OG images.&lt;/p&gt;

&lt;p&gt;The Twitter card images shown in the feed are a 1.91:1 ratio. ****If you're creating your own template, ensure to design it at the recommended resolution of 1200x630.&lt;/p&gt;

&lt;p&gt;As an example, here is a preview of the template I created for my blog. It contains the basic layout, a transparent section on the right-hand side for the article banner to be used as an underlay, and most importantly, contains the text that will remain constant for each social sharing image we create.&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%2Fres.cloudinary.com%2Fbraydoncoyer%2Fimage%2Fupload%2Fv1640023518%2Fdynamic_og_template_preview_tjknx7.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%2Fres.cloudinary.com%2Fbraydoncoyer%2Fimage%2Fupload%2Fv1640023518%2Fdynamic_og_template_preview_tjknx7.png" alt="For the purpose of this preview, I’ve included a visual transparent section of the template. When you export to png, this will not be visible."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the purpose of this preview, I’ve included a visual transparent section of the template. When you export to png, this will not be visible.&lt;/p&gt;

&lt;p&gt;Once you've found or created a template, upload the image to Cloudinary under the Media Library.&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%2Fres.cloudinary.com%2Fbraydoncoyer%2Fimage%2Fupload%2Fv1640023795%2Fdynamic_og_upload_cloudinary_vzpacl.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%2Fres.cloudinary.com%2Fbraydoncoyer%2Fimage%2Fupload%2Fv1640023795%2Fdynamic_og_upload_cloudinary_vzpacl.png" alt="https://res.cloudinary.com/braydoncoyer/image/upload/v1640023795/dynamic_og_upload_cloudinary_vzpacl.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Article images to Cloudinary
&lt;/h3&gt;

&lt;p&gt;It’s also important that your article images are hosted on Cloudinary so you can easily reference the image name when performing the transformation via the API.&lt;/p&gt;

&lt;p&gt;You can either upload images to Cloudinary from your computer, or use one of their integrated tools to discover and import images into your media library. I use the built-in Unsplash integration to add my article banners to my library, but you can use other tools like Shutterstock and iStock.&lt;/p&gt;

&lt;p&gt;With the template and article images uploaded to Cloudinary, we're ready to move to Next.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Shared SEO Component
&lt;/h2&gt;

&lt;p&gt;This part is optional depending on your setup. &lt;/p&gt;

&lt;p&gt;I tend to create reusable layout components that I consume on each page depending on the need and purpose. &lt;/p&gt;

&lt;p&gt;If you already have a preexisting Next.js project, you may already have a reusable layout component. Either way, here's the general idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a layout component to be used on your article pages.&lt;/li&gt;
&lt;li&gt;Pass children (the actual page content) and render accordingly.&lt;/li&gt;
&lt;li&gt;Pass meta information to be used for SEO purposes, including information which will be used with Cloudinary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example of a layout component I've created called &lt;code&gt;Container&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`bg-white dark:bg-dark min-h-screen'}&amp;gt;
      &amp;lt;NavMenu /&amp;gt;
      &amp;lt;main className="flex flex-col mx-auto max-w-6xl justify-center px-4 bg-white dark:bg-dark prose prose-lg md:prose-xl dark:prose-dark relative"&amp;gt;
        {children}
      &amp;lt;/main&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the snippet above, you can see that I have passed &lt;code&gt;children&lt;/code&gt; to this component which is rendered inside a &lt;code&gt;main&lt;/code&gt; element with appropriate Tailwind utility classes to achieve my desired layout for my blog.&lt;/p&gt;

&lt;p&gt;Since this component will be reused on every page of my application, we can also include SEO information and dynamically pass information based on which page is rendered.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Head&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/head&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;customMeta&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// create a router to be used in the meta object below&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My site&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A description about my site&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path-to-an-image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;twitterHandle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://twitter.com/BraydonCoyer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;canonicalUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://braydoncoyer.dev&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;customMeta&lt;/span&gt; &lt;span class="c1"&gt;// this replaces any properties that we pass to the component as props&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`bg-white dark:bg-dark min-h-screen'}&amp;gt;

            &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;{meta.title}&amp;lt;/title&amp;gt;
        &amp;lt;meta name="robots" content="follow, index" /&amp;gt;
        &amp;lt;meta content={meta.description} name="description" /&amp;gt;
        &amp;lt;meta
          property="og:url"
          content={`&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//braydoncoyer.dev${router.asPath}`}&lt;/span&gt;
        &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canonicalUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"og:site_name"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Braydon Coyer"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imageUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"summary_large_image"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"twitter:site"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitterHandle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"twitter:title"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"twitter:description"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"twitter:image"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imageUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"article:published_time"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NavMenu&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col mx-auto max-w-6xl justify-center px-4 bg-white dark:bg-dark prose prose-lg md:prose-xl dark:prose-dark relative"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this looks like a lot of code, we are simply crafting a meta object to be consumed inside the &lt;code&gt;Head&lt;/code&gt; component that Next.js exposes.&lt;/p&gt;

&lt;p&gt;This is enough to properly have your application leverage SEO: simply pass a few props to the &lt;code&gt;Container&lt;/code&gt; component and you should be good to go! &lt;/p&gt;

&lt;p&gt;However, notice that the &lt;code&gt;meta&lt;/code&gt; tags containing &lt;code&gt;og:image&lt;/code&gt; and &lt;code&gt;twitter:image&lt;/code&gt; using a static image URL. &lt;/p&gt;

&lt;p&gt;Let's make it dynamic with Cloudinary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Dynamic OG Image with the Cloudinary API
&lt;/h2&gt;

&lt;p&gt;Cloudinary's API supports text and image overlays, providing an easy way to dynamically transform images. &lt;/p&gt;

&lt;p&gt;Utilizing the API is as simple as appending variables to the URL of an image hosted on Cloudinary. &lt;/p&gt;

&lt;p&gt;In the end, the URL may look something 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;https://res.cloudinary.com/braydoncoyer/image/upload/w_1200,h_630,c_fill,f_auto/w_580,h_630,c_fill,u_learn_tailwindplay_banner.jpg/fl_layer_apply,g_east/w_630,h_450,c_fit,co_rgb:FFFFFF,g_west,x_45,y_-40,l_text:arial_60_bold:Learn%20Tailwind%20with%20TailwindPlay/og_social_large.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The URL is a bit cumbersome, but let me break it down from top to bottom:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;https://res.cloudinary.com/braydoncoyer/&lt;/code&gt; - a base URL containing my Cloudinary account name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;image/upload&lt;/code&gt; - the asset type.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;w_1200,h_630&lt;/code&gt; - the width and height for the entire image.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c_fill&lt;/code&gt; - crop mode.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_auto&lt;/code&gt; - automatically chooses the best format based upon which browser is being used.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;w_580,h_630&lt;/code&gt; - the size of the image underlay.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;u_learn_tailwindplay_banner.jpg&lt;/code&gt; - the name of the banner associated with the article.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fl_layer_apply&lt;/code&gt; - applies all chained transformations on the underlaid image.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;g_east&lt;/code&gt; - informs Cloudinary which sector on the image to place the underlay.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;w_630,h_450&lt;/code&gt; - the size of a text box&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;co_rgb:FFFFFF&lt;/code&gt; - specifies the text color&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;g_west,x_45,y_-40&lt;/code&gt; - determines which sector to place the text, and includes exact pixel positions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;text:arial_60_bold:&lt;/code&gt; - font name and size.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Learn%20Tailwind%20with%20TailwindPlay&lt;/code&gt; - the encoded text value to display on the left-side of the image.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;og_social_large.png&lt;/code&gt; - the name of the template uploaded to Cloudinary.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configure a function to generate the Cloudinary URL
&lt;/h3&gt;

&lt;p&gt;Manually creating a URL like this would be extremely tedious and time-consuming. To make the process easier, let's create a function to build the Cloudinary URL and return it to us. &lt;/p&gt;

&lt;p&gt;I've created a file called &lt;code&gt;generateSocialImage&lt;/code&gt; in my &lt;code&gt;lib&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateSocialImage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;cloudName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;imagePublicID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;cloudinaryUrlBase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://res.cloudinary.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;titleFont&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arial&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;titleExtraConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;underlayImageWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;580&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;underlayImageHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;underlayImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;imageWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;imageHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;textAreaWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;textAreaHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;textLeftOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;textBottomOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;textColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FFFFFF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;titleFontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// configure social media image dimensions, quality, and format&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;`w_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;imageWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`h_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;imageHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;c_fill&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;f_auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// configure the underlay - the actual article banner&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;underlayClonfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;`w_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;underlayImageWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`h_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;underlayImageHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`c_fill`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`u_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;underlayImage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/fl_layer_apply`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`g_east`&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// configure the title text&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;titleConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;`w_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;textAreaWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`h_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;textAreaHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;c_fit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`co_rgb:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;g_west&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`x_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;textLeftOffset&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`y_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;textBottomOffset&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`l_text:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;titleFont&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;titleFontSize&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;titleExtraConfig&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;title&lt;/span&gt;
    &lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// combine all the pieces required to generate a Cloudinary URL&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlParts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;cloudinaryUrlBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;cloudName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;imageConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;underlayClonfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;titleConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;imagePublicID&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// remove any falsy sections of the URL (e.g. an undefined version)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validParts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// join all the parts into a valid URL to the generated image&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;validParts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the most part, you can plug in your information and the function will work as expected. You can tinker with the destructured props to change the position of the text and image to fit your needs.&lt;/p&gt;

&lt;p&gt;I call this function on my article page, where I can pass the article title and banner image to the function. The function returns the new Cloudinary URL and is then provided to the &lt;code&gt;Container&lt;/code&gt; component. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📣 Recall that the &lt;code&gt;Container&lt;/code&gt; component hosts the &lt;code&gt;meta&lt;/code&gt; tags needed for proper search engine optimization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Please note the image named passed as &lt;code&gt;imagePublicID&lt;/code&gt; - this is the name of the template image uploaded to Cloudinary. Make sure you swap this name out to match the name of the template you uploaded in your Cloudinary media library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// [slug].ts&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socialImageConf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateSocialImage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;underlayImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;coverImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;coverImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastIndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;cloudName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;braydoncoyer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imagePublicID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;og_social_large.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// the OG template image name uploaded in Cloudinary &lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Container&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;socialImageConf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// pass the dynamic URL here&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publishedDate&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'article'&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        ...
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing your social sharing Open Graph images
&lt;/h2&gt;

&lt;p&gt;Once everything is hooked up and configured appropriately, you should be able to run your Next.js project ( &lt;code&gt;npm run dev&lt;/code&gt; ) and see the &lt;code&gt;meta&lt;/code&gt; tags on the DOM under the &lt;code&gt;head&lt;/code&gt; element.&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%2Fres.cloudinary.com%2Fbraydoncoyer%2Fimage%2Fupload%2Fv1640024175%2Fdynamic_og_constructued_url_ls7uma.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%2Fres.cloudinary.com%2Fbraydoncoyer%2Fimage%2Fupload%2Fv1640024175%2Fdynamic_og_constructued_url_ls7uma.png" alt="https://res.cloudinary.com/braydoncoyer/image/upload/v1640024175/dynamic_og_constructued_url_ls7uma.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look for the &lt;code&gt;og:image&lt;/code&gt; tag, copy the URL and paste it in a new tab. If everything works, you should see your new dynamic Open Graph image that will appear on social media outlets!&lt;/p&gt;

&lt;h3&gt;
  
  
  Using online tools to validate the Open Graph images
&lt;/h3&gt;

&lt;p&gt;Once your application is published, grab the full article slug and paste it into the textbox on &lt;a href="http://socialsharepreview.com" rel="noopener noreferrer"&gt;socialsharepreview.com&lt;/a&gt; - a tool that validates that your meta tags are correctly configured for the web.&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%2Fres.cloudinary.com%2Fbraydoncoyer%2Fimage%2Fupload%2Fv1640024270%2Fdynamic_og_check_preview_lkzcxh.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%2Fres.cloudinary.com%2Fbraydoncoyer%2Fimage%2Fupload%2Fv1640024270%2Fdynamic_og_check_preview_lkzcxh.png" alt="https://res.cloudinary.com/braydoncoyer/image/upload/v1640024270/dynamic_og_check_preview_lkzcxh.png"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;And with that - you've now created a system that dynamically creates Open Graph images for social outlets using Cloudinary and Next.js! &lt;/p&gt;

&lt;p&gt;If you've made it this far and completed this article, I'd love for you to reach out to me on &lt;a href="https://twitter.com/BraydonCoyer" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and send me a link to your blog or website so I can see the Open Graph images at work!  &lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.contentful.com/blog/2021/09/08/personalized-image-social-sharing-with-cloudinary-nextjs/" rel="noopener noreferrer"&gt;How to build a personalized image social sharing app with Cloudinary and Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.juanfernandes.uk/blog/automated-open-graph-images-with-11ty-and-cloudinary/" rel="noopener noreferrer"&gt;Automated Open Graph Images with 11ty and Cloudinary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://urre.me/writings/dynamic-open-graph-images/" rel="noopener noreferrer"&gt;Dynamic Open Graph Images&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>cloudinary</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
