<?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: Jordan Wells</title>
    <description>The latest articles on DEV Community by Jordan Wells (@jordantwells42).</description>
    <link>https://dev.to/jordantwells42</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%2F893092%2F50757cd1-d0aa-41f9-a4c3-c0e63984a78a.jpeg</url>
      <title>DEV Community: Jordan Wells</title>
      <link>https://dev.to/jordantwells42</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jordantwells42"/>
    <language>en</language>
    <item>
      <title>They're a 10 but they use Redis as their primary database - They're still a 10!</title>
      <dc:creator>Jordan Wells</dc:creator>
      <pubDate>Mon, 15 Aug 2022 21:17:00 +0000</pubDate>
      <link>https://dev.to/jordantwells42/theyre-a-10-but-they-use-redis-as-their-primary-database-theyre-still-a-10-17op</link>
      <guid>https://dev.to/jordantwells42/theyre-a-10-but-they-use-redis-as-their-primary-database-theyre-still-a-10-17op</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;Hello everyone! I built a popular TikTok trend using &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;, &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;, &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;, and &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;! Check it out a &lt;a href="https://10but.jordantwells.com" rel="noopener noreferrer"&gt;https://10but.jordantwells.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This website takes the popular TikTok trend of "They're a 10 but..." and turns it into a fun online game where you can see what other people think! For those unfamiliar with the trend, the basic idea is that you give someone a characteristic about someone and how attractive they are, and then they give you back what they would rate them out of 10. So for example, if someone is a 5 but they know how to use RedisJSON, then they are easily a 10/10!&lt;/p&gt;

&lt;p&gt;It is built using Next.js, which handles routing between the main voting page, the results page, and the "add a new prompt" page. Tailwind CSS is used to build the styling and hover states for the page. tRPC is used as a type-safe layer between my TypeScript backend and my TypeScript frontend. And of course, Redis is used as my primary database.&lt;/p&gt;

&lt;p&gt;I use Redis for two primary purposes. &lt;/p&gt;

&lt;p&gt;The first is as a store for all of the prompts. I use a Redis list with the keyword "prompts" to store all of this information. If it currently has nothing in it, I add some great default prompts that I found on TikTok and twitter. If a user adds a new prompt, it will be "lpush"ed onto the list, and whenever a user is on the voting page a random prompt is grabbed from the full list.&lt;/p&gt;

&lt;p&gt;The second use is as a store for all of the ratings. Whenever a user submits a vote, it saves the prompt, appearance rating, user-given rating, and difference between the two ratings into RedisJSON. I then can get the ratings and perform some data modifications to get an averaged change from appearance rating to user-given rating based on the prompt. This information is displayed on the results page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;Wacky Wildcards&lt;/p&gt;

&lt;h3&gt;
  
  
  Language Used
&lt;/h3&gt;

&lt;p&gt;JS/TS/Node.js&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jordantwells42" rel="noopener noreferrer"&gt;
        jordantwells42
      &lt;/a&gt; / &lt;a href="https://github.com/jordantwells42/10but" rel="noopener noreferrer"&gt;
        10but
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The popular TikTok trend "They're a 10 but..." implemented with Next.js and Redis
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;They're a 10 but..&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This website takes the popular trend of "They're a 10 but..." and turns it into a fun online game where you can see what other people think! For those unfamiliar with the trend, the basic idea is that you give someone a characteristic about someone and how attractive they are, and then they give you back what they would rate them out of 10. So for example, if someone is a 5 but they know how to use RedisJSON, then they are easily a 10/10!&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/8213365/184719265-0e4711e9-ca90-4fbc-9357-ba17f8a9fc8a.png"&gt;&lt;img width="1096" alt="TB1" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F8213365%2F184719265-0e4711e9-ca90-4fbc-9357-ba17f8a9fc8a.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/8213365/184719266-2cce8aff-f17c-4d2d-8837-025fdffdadd2.png"&gt;&lt;img width="965" alt="TB3" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F8213365%2F184719266-2cce8aff-f17c-4d2d-8837-025fdffdadd2.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/8213365/184719264-f1bb1374-36c1-479e-9b0f-044f1f0adf43.png"&gt;&lt;img width="984" alt="TB2" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F8213365%2F184719264-f1bb1374-36c1-479e-9b0f-044f1f0adf43.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;It is built using Next.js, which handles routing between the main voting page, the results page, and the "add a new prompt" page. Tailwind CSS is used to build the styling and hover states for the page. tRPC is used as a type-safe layer between my TypeScript backend and my TypeScript frontend. And of course, Redis is used…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jordantwells42/10but" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;This project was built using the T3 stack! It and all of the technologies can be found and used at &lt;a href="https://github.com/t3-oss/create-t3-app" rel="noopener noreferrer"&gt;https://github.com/t3-oss/create-t3-app&lt;/a&gt;&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Check out &lt;a href="https://redis.io/docs/stack/get-started/clients/#high-level-client-libraries" rel="noopener noreferrer"&gt;Redis OM&lt;/a&gt;, client libraries for working with Redis as a multi-model database.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Use &lt;a href="https://redis.info/redisinsight" rel="noopener noreferrer"&gt;RedisInsight&lt;/a&gt; to visualize your data in Redis.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sign up for a &lt;a href="https://redis.info/try-free-dev-to" rel="noopener noreferrer"&gt;free Redis database&lt;/a&gt;.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>redishackathon</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Built a Tinder for Restaurants App — Here Are Some Web Development Techniques I Learned</title>
      <dc:creator>Jordan Wells</dc:creator>
      <pubDate>Mon, 08 Aug 2022 21:08:00 +0000</pubDate>
      <link>https://dev.to/jordantwells42/web-dev-tips-i-learned-from-build-the-tinder-of-finding-a-restaurant-4dll</link>
      <guid>https://dev.to/jordantwells42/web-dev-tips-i-learned-from-build-the-tinder-of-finding-a-restaurant-4dll</guid>
      <description>&lt;p&gt;This week, I aimed to solve the age old question: Where should I eat? Here’s what I learned from it.&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%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2A6_MSnV0GNB6_y2x3Ou95Fg.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%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2A6_MSnV0GNB6_y2x3Ou95Fg.png" alt="The results page for Where Should I https://whereshouldieat.app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This week, I aimed to solve the age old question: &lt;a href="https://whereshouldieat.app" rel="noopener noreferrer"&gt;&lt;em&gt;Where should I eat&lt;/em&gt;&lt;/a&gt;&lt;em&gt;?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Along the way, I learned three important tips about web development that I will carry with me to everything else that I decide to make.&lt;/p&gt;

&lt;p&gt;I’ll first talk about the app I made to give you some context, and then I’ll dive into problems I was presented and what I learned by fixing them.&lt;/p&gt;

&lt;p&gt;You can try out the site yourself at &lt;a href="https://whereshouldieat.app" rel="noopener noreferrer"&gt;whereshouldieat.app&lt;/a&gt; and as always all of the code can be found on &lt;a href="https://github.com/jordantwells42/whereshouldieat" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Where Should I Eat?&lt;/strong&gt;
&lt;/h3&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%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AZ4PnOk0SYFZS5KWjlycZDA.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%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AZ4PnOk0SYFZS5KWjlycZDA.png" alt="Searching for a craving at https://whereshouldieat.app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, solutions for this already exist. You can pull up Google search and fire out a quick search for “&lt;a href="https://www.google.com/search?q=sushi+near+me" rel="noopener noreferrer"&gt;sushi near me&lt;/a&gt;”, or look up an article about “&lt;a href="https://www.malaproject.nyc/" rel="noopener noreferrer"&gt;top restaurants in Manhattan.&lt;/a&gt;” However, both of these solutions answer the question by forcing you to make an even harder decision between many choices, some of which you may have never heard of before.&lt;/p&gt;

&lt;p&gt;My site alleviates this by only giving you one decision at a time: whether you want to eat at this restaurant or not. If you don’t want to eat there, you shouldn’t need to think about it anymore. If you do want to eat there, you should be be immediately taken to directions on how to get there.&lt;/p&gt;

&lt;p&gt;These choices are encoded into gestures. A swipe left means to ignore that restaurant and no longer consider it. A swipe right leads to the Google Maps page for that restaurant where you can easily find directions. You only ever have to make one decision at time, making the whole process as streamlined for someone as indecisive as me.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Slowing down your app can speed up the user experience
&lt;/h3&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%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AnnsqcsJDZegfX0YEYBKBIA.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%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AnnsqcsJDZegfX0YEYBKBIA.png" alt="Searching for a location on (https://whereshouldieat.app)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Responsive inputs can feel amazing. As soon as you start typing in an input field, autosuggestions can pop up and other queries can start happening. However, some things just shouldn’t happen on every key stroke.&lt;/p&gt;

&lt;p&gt;For my app, I initially had the text input set up so that every key stroke would fetch from the Yelp API, find and set locations and businesses, and change the location on the map. This caused the map to jostle around and load several towns based off partial inputs. Trying to search for “midtown Manhattan” would lead to a search for “m” and “mi” and “mid” and every other possible substring starting from the beginning. &lt;/p&gt;

&lt;p&gt;My solution to this was a concept called &lt;a href="https://www.freecodecamp.org/news/javascript-debounce-example/" rel="noopener noreferrer"&gt;&lt;em&gt;debouncing&lt;/em&gt;&lt;/a&gt;. This makes it so that it is physically impossible for the function to be called several times within a certain time interval, effectively throttling the relationship between the input and its effects. Now the search only fires once the user has cooled off on typing in their input, giving the expected results.&lt;/p&gt;

&lt;p&gt;In terms of implementation, I found &lt;a href="https://blog.logrocket.com/how-and-when-to-debounce-or-throttle-in-react/" rel="noopener noreferrer"&gt;this article&lt;/a&gt; very helpful with ways to do it with React primitives. Of course, someone has made a library for debouncing input fields called &lt;a href="https://www.npmjs.com/package/react-debounce-input" rel="noopener noreferrer"&gt;react-debounce-input&lt;/a&gt;, so I used that instead. Implementation is as easy as replacing inputs with DebounceInputs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;MyDebounceSearch&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DebounceInput&lt;/span&gt;
          &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&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="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;debounceTimeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&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;h3&gt;
  
  
  &lt;strong&gt;2. Your first user shouldn’t come with deployment&lt;/strong&gt;
&lt;/h3&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%2Fcdn-images-1.medium.com%2Fmax%2F800%2F0%2AovFkrNynF5Ri-SOA" 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%2Fcdn-images-1.medium.com%2Fmax%2F800%2F0%2AovFkrNynF5Ri-SOA" alt="Photo by Jakub Dziubak on Unsplash"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This may sound obvious, but you are the first person to use your site. And according to &lt;a href="https://lawsofux.com/en/jakobs-law/" rel="noopener noreferrer"&gt;Jakob’s law&lt;/a&gt;, you have spent MUCH more time on sites other than the one you just started developing yesterday. You’re a good judge of how sites work and how your new site should behave. So, use it! &lt;/p&gt;

&lt;p&gt;Even as you’re developing, be the user and interact with your site constantly. And if something is confusing to your user-brain, put on your developer hat and fix it!&lt;/p&gt;

&lt;p&gt;In the case of Where Should I Eat, I found this most evident with how I handled the URL. I used the barely-developed version of the site whenever I was out with friends, and I found myself confused whenever I tried to press the back button on my browser and it completely navigated me away from the site. The internet has taught me that if I want to undo, I can navigate back and expect the change to be undone.&lt;/p&gt;

&lt;p&gt;To fix this, &lt;a href="https://www.youtube.com/watch?v=sFTGEs2WXQ4" rel="noopener noreferrer"&gt;I lifted my state into the URL&lt;/a&gt;. Now, whenever I major change was made to the location of search or the food that the user was craving, this would be reflected as a &lt;a href="https://www.botify.com/learn/basics/what-are-url-parameters" rel="noopener noreferrer"&gt;URL parameter.&lt;/a&gt; So, if a user decided they weren’t feeling tacos and they wanted to go back to their previous choice of sushi, they could naturally do that by navigating back on their browser. &lt;/p&gt;

&lt;p&gt;Quick aside: If you’re using Next.js and it’s router to handle putting state into the URL, I would recommend using &lt;a href="https://nextjs.org/docs/routing/shallow-routing" rel="noopener noreferrer"&gt;shallow routing&lt;/a&gt; and setting the scroll option to false if you’re finding issues with state changes jerking your site’s scroll or data around.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;useEffect&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="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="c1"&gt;// Current URL is '/'&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&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;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="nf"&gt;useEffect&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;// Always do navigations after the first render&lt;/span&gt;
    &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/?counter=10&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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="na"&gt;shallow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scroll&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;span class="nf"&gt;useEffect&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;// The counter changed!&lt;/span&gt;
  &lt;span class="p"&gt;},&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&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="nx"&gt;Page&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;3. Compromise with cunning&lt;/strong&gt;
&lt;/h3&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%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AwyIMatDJPFdwmSQrMLvTfw.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%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AwyIMatDJPFdwmSQrMLvTfw.png" alt="The pricing for a relatively modest 10,000 requests a month"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Originally, I wanted to use the &lt;a href="https://mapsplatform.google.com/pricing/" rel="noopener noreferrer"&gt;Google Maps API&lt;/a&gt;. I was quickly met with a credit card form and a pricing scheme that scares the part of me that hopes my app is an overnight success. And this isn’t even including using their map tiling for generating the dynamic map. (They do provide $200/month of requests free but I learned that &lt;em&gt;after&lt;/em&gt; I started my site.)&lt;/p&gt;

&lt;p&gt;Instead, I reached for free alternatives. The &lt;a href="https://fusion.yelp.com/" rel="noopener noreferrer"&gt;Yelp Fusion API&lt;/a&gt; provides me with place information such as reviews, locations, and hours of business with a solid 5,000 requests/day without upgrading to an enterprise plan. Additionally, the &lt;a href="https://www.npmjs.com/package/pigeon-maps/v/0.16.1" rel="noopener noreferrer"&gt;pigeon-maps&lt;/a&gt; package leverages free tiling solutions such as &lt;a href="http://OpenStreetMap.org" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt; to provide dynamic and well-tooled maps for React.&lt;/p&gt;

&lt;p&gt;Of course, I still wanted to use Google Maps at the end of the day. In order to still get that integration, I simply used the queried latitude, longitude, and place name from Yelp to construct a Google Maps URL that would take me right to the correct place. There is a &lt;a href="https://moz.com/blog/new-google-maps-url-parameters" rel="noopener noreferrer"&gt;helpful guide&lt;/a&gt; I found for doing that. &lt;/p&gt;

&lt;p&gt;Now I’m able to get similar functionality without breaking the bank and with no noticeable hits to the user experience. I’ve comprised on not using the Google Maps API but have lost no major features to my app!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;As I’ve continued to build more projects, I found the issues I face become more and more subtle. At first, I was yelling at JavaScript. Then, I was confused by hooks and whatever the heck “state” is. Eventually, I was yelling at TypeScript. Now, I find my issues to be more like crafting a clean user experience, fetching data in a way that prevents &lt;a href="https://en.wikipedia.org/wiki/Waterfall_chart#:~:text=A%20waterfall%20chart%20is%20a,time%20based%20or%20category%20based." rel="noopener noreferrer"&gt;waterfalls&lt;/a&gt;, or making an aesthetically pleasing design.&lt;/p&gt;

&lt;p&gt;I hope to level-up my development so that the only issue I have is coming up with the next &lt;a href="https://en.wikipedia.org/wiki/Unicorn_%28finance%29" rel="noopener noreferrer"&gt;unicorn startup&lt;/a&gt; idea! And I hope to share some of the wisdom I learn along the way with all of my readers!&lt;/p&gt;

&lt;p&gt;If you want to follow along with my programming journey, follow me here on &lt;a href="https://medium.com/@jordantwells" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; and check out my personal site at &lt;a href="https://jordantwells.com" rel="noopener noreferrer"&gt;jordantwells.com&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Jordan&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>Creating a full-fledged Spotify Playlist Generator in a Weekend</title>
      <dc:creator>Jordan Wells</dc:creator>
      <pubDate>Wed, 20 Jul 2022 12:56:00 +0000</pubDate>
      <link>https://dev.to/jordantwells42/creating-a-full-fledged-spotify-playlist-generator-in-a-weekend-137p</link>
      <guid>https://dev.to/jordantwells42/creating-a-full-fledged-spotify-playlist-generator-in-a-weekend-137p</guid>
      <description>&lt;p&gt;Recently, I've wanted to better understand how &lt;a href="https://aws.amazon.com/what-is/api/"&gt;APIs&lt;/a&gt; work. I've used some simple ones that will &lt;a href="https://www.colr.org/api.html"&gt;generate random colors&lt;/a&gt; for you, or give you a &lt;a href="https://kinduff.github.io/dog-api/"&gt;random dog fact&lt;/a&gt; (did you know that perky-eared dogs hear sounds better than floppy-eared dogs?). APIs can be a wonderful tool to get data for your web app, but almost all of the more sophisticated ones require &lt;a href="https://auth0.com/docs/get-started/identity-fundamentals/authentication-and-authorization"&gt;authentication and authorization&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To this end, I decided to use &lt;a href="https://developer.spotify.com"&gt;Spotify's Web API&lt;/a&gt;. It provides extensive documentation and allows you to do nearly anything a user can do on the platform; search for songs, create playlists, get recommendations, and much much more. &lt;/p&gt;

&lt;p&gt;The app I made is called Vibesition, and it allows a user to make a playlist that seamlessly transitions from one song's vibe to another. Its easily the my favorite web dev project that I have ever made. &lt;/p&gt;

&lt;p&gt;Try it out yourself at &lt;a href="https://vibesition.jordantwells.com"&gt;vibesition.jordantwells.com&lt;/a&gt;! All of the source code for it can be found on my &lt;a href="https://github.com/jordantwells42"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication using NextAuth.js
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NlEI5Vug--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/8213365/179861067-4cb0bac6-62da-4c8f-b975-e94bd1b7a401.png" class="article-body-image-wrapper"&gt;&lt;img alt="Searching for a song using Vibesition" src="https://res.cloudinary.com/practicaldev/image/fetch/s--NlEI5Vug--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/8213365/179861067-4cb0bac6-62da-4c8f-b975-e94bd1b7a401.png" width="880" height="409"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://next-auth.js.org"&gt;NextAuth&lt;/a&gt; is a tool for Next.js that makes integrating with popular sign-in services such as GitHub, Facebook, Discord, and &lt;a href="https://next-auth.js.org/configuration/providers/oauth"&gt;many more&lt;/a&gt; as simple as possible. I would highly recommend using &lt;a href="https://create.t3.gg"&gt;create-t3-app&lt;/a&gt; to initialize your project, as it can setup much of the boilerplate of NextAuth for you.&lt;/p&gt;

&lt;p&gt;The only changes you need to make is to the [...nextauth].ts&lt;br&gt;
file, to use the &lt;a href="https://next-auth.js.org/providers/spotify"&gt;SpotifyProvider&lt;/a&gt; and give your app the proper &lt;a href="https://developer.spotify.com/documentation/general/guides/authorization/scopes/"&gt;authorization scope&lt;/a&gt;. The Client ID and Client Secret both come from registering an application with the &lt;a href="https://developer.spotify.com/dashboard/"&gt;Spotify Developer Dashboard&lt;/a&gt;. While you're here, make sure to add something akin to "&lt;a href="http://localhost:3000/api/auth/callback/spotify"&gt;http://localhost:3000/api/auth/callback/spotify&lt;/a&gt;" to your Redirect URIs to ensure that you can sign in correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;NextAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NextAuthOptions&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="s2"&gt;next-auth&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;SpotifyProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SpotifyProfile&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="s2"&gt;next-auth/providers/spotify&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;CredentialsProvider&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth/providers/credentials&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;const&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextAuthOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Configure one or more authentication providers&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="nx"&gt;SpotifyProvider&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://accounts.spotify.com/authorize?scope=user-read-email,playlist-modify-public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientId&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;SPOTIFY_CLIENT_ID&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&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;SPOTIFY_CLIENT_SECRET&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&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;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;})&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;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&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;token&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;secret&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;NEXT_AUTH_SECRET&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&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="nx"&gt;NextAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case I chose a scope that allows me to the see the user's email address, and modify their public playlists, just as much as I would need for &lt;a href="https://vibesition.jordantwells.com"&gt;Vibesition&lt;/a&gt;. To actually consume this API, the authorization code needs to be exchanged for an access token which will be your ticket to use any endpoint within your defined scope. More details about this process can be found on &lt;a href="https://developer.spotify.com/documentation/general/guides/authorization/code-flow/"&gt;Spotify's documentation&lt;/a&gt;. Note that this refresh token comes from the token generated by NextAuth&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client_id&lt;/span&gt; &lt;span class="o"&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;SPOTIFY_CLIENT_ID&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client_secret&lt;/span&gt; &lt;span class="o"&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;SPOTIFY_CLIENT_SECRET&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authorization_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&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;client_id&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;client_secret&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;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&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="nx"&gt;getAccessToken&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;refresh_token&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="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;response&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;`https://accounts.spotify.com/api/token`&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="nx"&gt;authorization_code&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/x-www-form-urlencoded&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;grant_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;refresh_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;refresh_token&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;response&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUsersPlaylists&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;refresh_token&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="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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;access_token&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;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&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;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://api.spotify.com/v1/me/playlists&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;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;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;access_token&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="p"&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;const&lt;/span&gt; &lt;span class="nx"&gt;getSearch&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;refresh_token&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="nx"&gt;query&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="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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;access_token&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;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&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;querystring&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;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;q&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="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;track&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;market&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;toString&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SEARCH_ENDPOINT&lt;/span&gt; &lt;span class="o"&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="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;querystring&lt;/span&gt;&lt;span class="p"&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;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;access_token&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="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;Note that the "refreshToken" parameter comes from NextAuth, and with my setup can be gotten by doing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&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;getToken&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building the App
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KQjlXWxx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/8213365/179861119-de0b5cb3-6acc-4c39-9182-9869a91ea756.png" class="article-body-image-wrapper"&gt;&lt;img alt="Selecting a song with Vibesition" src="https://res.cloudinary.com/practicaldev/image/fetch/s--KQjlXWxx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/8213365/179861119-de0b5cb3-6acc-4c39-9182-9869a91ea756.png" width="880" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that I had the power of Spotify's API behind me (within my small scope that I defined), I could finally get to work building my web app. &lt;/p&gt;

&lt;p&gt;I wanted users to be able to search songs to act as the starting and ending points for their playlist. To do this, I used the &lt;a href="https://developer.spotify.com/documentation/web-api/reference/#/operations/search"&gt;search endpoint&lt;/a&gt; to fetch the top 5 responses from Spotify search. Once a user selects a song as shown above, it saves the Spotify ID in React state. This ID is used as a query for many of the other endpoints, so it's a good thing to save.&lt;/p&gt;

&lt;p&gt;Once a user selects a song it automatically generates a color based on the ~vibe~ of the song using the &lt;a href="https://developer.spotify.com/documentation/web-api/reference/#/operations/get-audio-features"&gt;Spotify Audio Features&lt;/a&gt; endpoint. Specifically, I use a combination of the song's danceability, energy, and valence (I don't know what it means either) to determine the RGB values.&lt;/p&gt;

&lt;p&gt;To determine what songs will be in the playlist, I use the &lt;a href="https://developer.spotify.com/documentation/web-api/reference/#/operations/get-recommendations"&gt;Get Recommendations&lt;/a&gt; endpoint. This allows you to find songs based off target features, such as danceability, tempo, key, duration and many more. In this case I use a &lt;a href="https://en.wikipedia.org/wiki/Sigmoid_function"&gt;sigmoid-like interpolation&lt;/a&gt; to interpolate between the two song's values of the audio features and get 10 songs with target values on that curve.&lt;/p&gt;

&lt;p&gt;Finally, to create the playlist, I use both the &lt;a href="https://developer.spotify.com/documentation/web-api/reference/#/operations/create-playlist"&gt;create playlist&lt;/a&gt; and &lt;a href="https://developer.spotify.com/documentation/web-api/reference/#/operations/add-tracks-to-playlist"&gt;add items to playlist&lt;/a&gt; end points. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KOPI6MsA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/8213365/179861274-ce8ad617-613d-49fc-8146-7807ce8a920a.png" class="article-body-image-wrapper"&gt;&lt;img alt="Generating a playlist with Vibesition" src="https://res.cloudinary.com/practicaldev/image/fetch/s--KOPI6MsA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/8213365/179861274-ce8ad617-613d-49fc-8146-7807ce8a920a.png" width="880" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There was a whole bunch of other neat things added into this project as well. I used &lt;a href="https://www.framer.com/motion/"&gt;Framer Motion&lt;/a&gt; to give the entire project some much needed life. I used &lt;a href="https://www.w3schools.com/html/html5_audio.asp"&gt;HTML audio tags&lt;/a&gt; to allow users to play songs if they had a preview url from Spotify. And of course I used &lt;a href="https://tailwindcss.com"&gt;Tailwind CSS&lt;/a&gt; to make making the UI the simplest part of the process.&lt;/p&gt;

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

&lt;p&gt;Overall, this was an incredibly fulfilling project to learn about using high-impact APIs. I hope to use many more in the future and incorporate them into my projects to make getting interesting data easy. If you're interested in more of my projects or me, check out my personal website, &lt;a href="https://jordantwells.com"&gt;jordantwells.com&lt;/a&gt;. &lt;/p&gt;

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

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Made an Automatic Color Theme Generator for Tailwind CSS</title>
      <dc:creator>Jordan Wells</dc:creator>
      <pubDate>Sun, 17 Jul 2022 11:53:54 +0000</pubDate>
      <link>https://dev.to/jordantwells42/i-made-an-automatic-color-theme-generator-for-tailwind-css-1gbk</link>
      <guid>https://dev.to/jordantwells42/i-made-an-automatic-color-theme-generator-for-tailwind-css-1gbk</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T22AFOxt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2ANBk7w9p9esfycQee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T22AFOxt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2ANBk7w9p9esfycQee.png" alt="The landing page for huewind.jordantwells.com" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have recently been obsessed with the &lt;a href="https://create.t3.gg"&gt;T3 stack&lt;/a&gt; (&lt;a href="https://nextjs.org"&gt;Next.js&lt;/a&gt;, &lt;a href="https://www.typescriptlang.org"&gt;TypeScript&lt;/a&gt;, &lt;a href="https://tailwindcss.com"&gt;Tailwind CSS&lt;/a&gt;, &lt;a href="https://www.prisma.io"&gt;Prisma&lt;/a&gt;, and &lt;a href="https://trpc.io"&gt;tRPC&lt;/a&gt;). As part of that I have been using Tailwind CSS to quickly layout the format of &lt;em&gt;every&lt;/em&gt; project I have been working on for the past month. It makes styling as symbol as writing classes into your &lt;a href="https://reactjs.org/docs/introducing-jsx.html"&gt;JSX&lt;/a&gt; or HTML.&lt;/p&gt;

&lt;p&gt;But I wanted more options. The default colors provided by the Tailwind team are &lt;a href="https://tailwindcss.com/docs/customizing-colors"&gt;&lt;em&gt;wonderful&lt;/em&gt;&lt;/a&gt;, but I’ve started too many projects with ‘&lt;em&gt;bg-slate-700&lt;/em&gt;’. I decided it was time for me to change something. And what better way to do that than to make my own tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huewind.jordantwells.com"&gt;HueWind&lt;/a&gt; is a site devoted to making cohesive Tailwind CSS themes as easy as possible. Drawing inspiration from &lt;a href="https://lospec.com/palette-list"&gt;pixel art palette creators&lt;/a&gt;, I placed a heavy emphasis on clean blending between a theme’s lightest value, primary colors, and darkest value.&lt;/p&gt;

&lt;p&gt;Try it out here, &lt;a href="http://huewind.jordantwells.com"&gt;huewind.jordantwells.com&lt;/a&gt; , and as always the source code can be found on my GitHub at &lt;a href="http://GitHub.com/jordantwells42/huewind"&gt;github.com/jordantwells42/huewind&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FpFDq6VJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2AhCQ9ySq_VJ6A7jlj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FpFDq6VJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2AhCQ9ySq_VJ6A7jlj.png" alt="Choosing complementary colors at huewind.jordantwells.com" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Why use this over any other palette generator?
&lt;/h4&gt;

&lt;p&gt;The key behind this project is its &lt;em&gt;awareness&lt;/em&gt; of all the colors on your site. Typical color swatches are generated in a &lt;em&gt;vacuum,&lt;/em&gt; where base colors are unaware of their relationship to other base colors, and the entire swatch is unaware of its relationship to the lightest and darkest tones on the website.&lt;/p&gt;

&lt;p&gt;HueWind automatically generates complementary theme colors for you as part of a tetradic color scheme. This ensures a clear relationship between your primary theme color and the ones that support it.&lt;/p&gt;

&lt;p&gt;More importantly, color swatches are generated with respect to the lightest and darkest tones on your site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oHFOolZk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2A5PSGMdfvUWPFMfvB.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oHFOolZk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2A5PSGMdfvUWPFMfvB.png" alt="Choosing the lightest and darkest tones for your site at huewind.jordantwells.com" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Typical autogenerated color swatches will transition your target color towards a pure white (#FFFFFF) to generate lighter shades and a pure black (#000000) to generate darker ones. While this design can look modern, it also lacks a lot of the soul that hand-picked artistic palettes have.&lt;/p&gt;

&lt;p&gt;HueWind instead looks to your chosen lightest shade and darkest shade to create its swatches. A simple change from a pure black to a deep purple or a blinding white to a pale yellow can have wonderful changes to the aesthetic and feel of a website.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SsdUxj91--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2A1HiYZCFXWEDQJyXf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SsdUxj91--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/0%2A1HiYZCFXWEDQJyXf.png" alt="A palette generation by HueWind" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;How does it work?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;HueWind uses &lt;a href="https://en.wikipedia.org/wiki/Spline_interpolation"&gt;spline interpolation&lt;/a&gt; to seamlessly transition between the hues, saturations, and values of your light, dark, and base shades.&lt;/p&gt;

&lt;p&gt;This uses the 3 points of your light, base, and dark shades to uniquely determine a curve that passes through all of them, setting the X value of the shades to 0, 500, and 1000 respecitvely. It then evaluates this curve at the classic &lt;a href="https://tailwindcss.com/docs/customizing-colors"&gt;50–900 values of Tailwind CSS&lt;/a&gt;, giving the final output values.&lt;/p&gt;

&lt;p&gt;Importantly, for accessibility I automatically choose an accessible text color all across the site using &lt;a href="https://github.com/bgrins/TinyColor"&gt;tinycolor&lt;/a&gt;. This includes a fallback to using pure black and pure white in case that user decides that their favorite light shade is white and their favorite dark shade is white.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EYvd8wIr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AXWf4VP2dtkKP9LAGk-G6nA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EYvd8wIr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AXWf4VP2dtkKP9LAGk-G6nA.png" alt="The immediately usable Tailwind CSS config generated by HueWind" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Try it out!&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;It takes less than a minute to have a palette generated! Use it here at &lt;a href="http://huewind.jordantwells.com"&gt;huewind.jordantwells.com&lt;/a&gt; ! And I’m sure you’ll see me using it on my future projects on &lt;a href="http://jordantwells.com"&gt;jordantwells.com&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;Jordan&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>react</category>
    </item>
    <item>
      <title>How I created a Responsive Personal Portfolio Website with Next.js</title>
      <dc:creator>Jordan Wells</dc:creator>
      <pubDate>Sun, 17 Jul 2022 11:53:31 +0000</pubDate>
      <link>https://dev.to/jordantwells42/how-i-created-a-responsive-personal-portfolio-website-with-nextjs-197k</link>
      <guid>https://dev.to/jordantwells42/how-i-created-a-responsive-personal-portfolio-website-with-nextjs-197k</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JH2IuVUw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AU6s8mX7o40W7yRVSeCLjJg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JH2IuVUw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AU6s8mX7o40W7yRVSeCLjJg.png" alt="The final result, jordantwells.com" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Almost &lt;a href="https://medium.com/@jordantwells/how-i-created-a-responsive-personal-website-with-django-and-bootstrap-in-a-week-9f1c102e8137"&gt;two years ago&lt;/a&gt;, I created my &lt;a href="http://jordantwells.herokuapp.com/"&gt;first website&lt;/a&gt; using HTML, CSS, and Python. Now, I’ve reinvented it using a modern and robust front-end development framework known as &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;. For the past week, I have been developing it and now I’m proud to present &lt;a href="http://www.jordantwells.com."&gt;www.jordantwells.com.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I created the first website to learn the “ins and outs” of web development fundamentals, and now I’ve created this new website to learn how front-end development is done with a slick JavaScript framework. It definitely wasn’t an easy journey, but I hope I can pass on some of the cool things that I’ve learned about along the way.&lt;/p&gt;

&lt;p&gt;As always, if you want to see any of the code that made all this possible, it is available on my &lt;a href="https://github.com/jordantwells42"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is Next.js great?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vsw-iBJ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AevWiCwVffl0RCeaqayMFxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vsw-iBJ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AevWiCwVffl0RCeaqayMFxw.png" alt="Next.js logo" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; is a framework built on top of &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;, the popular JavaScript front-end development library. The benefit? Statically generated pages are reactive, and Next.js will hydrate your application client-side to give it full interactivity.&lt;/p&gt;

&lt;p&gt;Additionally, there are many ways to &lt;a href="https://nextjs.org/docs/basic-features/pages"&gt;render&lt;/a&gt; your site. Content can be rendered by the server (Server-Side Rendering), pre-rendered whenever you build the application, or deferred until a user decides to access the page.&lt;/p&gt;

&lt;p&gt;What does all this mean?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;em&gt;Faster development times&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; &lt;em&gt;Improved performance&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; &lt;em&gt;Better user experience&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sounds pretty good to me! There’s a reason why it is used by &lt;a href="https://www.netflix.com/"&gt;Netflix&lt;/a&gt;, &lt;a href="https://www.doordash.com/"&gt;DoorDash&lt;/a&gt;, &lt;a href="https://www.twitch.tv/"&gt;Twitch&lt;/a&gt;, &lt;a href="https://www.hulu.com/welcome?orig_referrer=https%3A%2F%2Fwww.google.com%2F"&gt;Hulu&lt;/a&gt;, and &lt;a href="https://nextjs.org/showcase"&gt;many more&lt;/a&gt;. So lets get cracking on developing one of these apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting started with Next.js&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before we begin, I’d highly recommend checking out the &lt;a href="https://nextjs.org/learn/basics/create-nextjs-app"&gt;Next.js documentation&lt;/a&gt;, which does an amazing job explaining all the ins and outs of the framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Creating a Next.js project with&lt;/strong&gt; &lt;a href="https://nextjs.org/docs/api-reference/create-next-app"&gt;&lt;strong&gt;create-next-app&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thankfully, the kind people behind Next.js have made it easy to get started with a bare bones Next.js app. Similar to the ever popular &lt;a href="https://create-react-app.dev/"&gt;create-react-app&lt;/a&gt;, you can get Next.js up and running with just one simple command. Simply &lt;em&gt;run one&lt;/em&gt; of the following commands in your terminal, and you should be greeted with a fully functioning Next.js app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;npx create-next-app@latest 
yarn create next-app
pnpm create next-app
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Setting up the development environment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then, all you need to do is navigate into your new project folder, and start the development server.&lt;/p&gt;

&lt;p&gt;# Navigate to Next.js project folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd myApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;# Start development server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should start a locally hosted version of your Next.js app at somewhere like &lt;a href="http://localhost:3000/"&gt;http://localhost:3000/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Getting familiar with the layout&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rGoN9Syy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AIcKG5XjT-ZWMMkRE28uB7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rGoN9Syy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AIcKG5XjT-ZWMMkRE28uB7w.png" alt="The file structure of create-next-app" width="247" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a few important things to take note of on this page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/docs/basic-features/pages"&gt;&lt;strong&gt;Pages&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt; This folder defines all of the pages on your site. Each page has to export a React component which defines all of the html on that page. index.js is the landing page for your website, and any other JavaScript file in this folder will create a new page. For example, creating an &lt;em&gt;about.js&lt;/em&gt; file in this folder will create a new page at yourcoolwebsite.com/about.&lt;/p&gt;

&lt;p&gt;Additionally, created a folder within the &lt;em&gt;pages&lt;/em&gt; folder will create a new nested area on your site. For example, creating a &lt;em&gt;projects&lt;/em&gt; folder and then a file called &lt;em&gt;myproject.js&lt;/em&gt; inside that folder will create a new page at yourcoolwebsite.com/projects/myproject.&lt;/p&gt;

&lt;p&gt;Finally, you can create &lt;a href="https://nextjs.org/docs/basic-features/pages#pages-with-dynamic-routes"&gt;dynamic routes&lt;/a&gt; by using brackets. For example, say you have a blog and want to have pages based on the blog post id. This can be accomplished by having a file such as &lt;em&gt;blogs/[id].js.&lt;/em&gt; The brackets tell Next.js that url paths such as yourcoolwebsite.com/blogs/1 and yourcoolwebsite.com/blogs/2 should be supported.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/docs/basic-features/static-file-serving"&gt;&lt;strong&gt;Public&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt; This defines where all of your static files exist, with any file in this folder being accessible through the use of a proceeding slash. So to access an image called &lt;a href="https://developers.google.com/speed/webp"&gt;my_cool_image.webp&lt;/a&gt;, you would use &lt;em&gt;/my_cool_image.webp.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From here it is relatively standard React development! I would highly recommend &lt;a href="https://www.youtube.com/watch?v=bMknfKXIFA8&amp;amp;t=40160s"&gt;this tutorial&lt;/a&gt; by &lt;a href="http://freeCodeCamp.org"&gt;freeCodeCamp.org&lt;/a&gt; for everything you could ever want to learn about React, and the &lt;a href="https://nextjs.org/docs/getting-started"&gt;Next.js documentation&lt;/a&gt; for anything Next.js specific.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding some personal flare&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nrwiNZw8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AMbzDthHFvRoQVv_6yIs__Q.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nrwiNZw8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AMbzDthHFvRoQVv_6yIs__Q.gif" alt="The file structure of create-next-app" width="880" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To spice up the website a bit, I created a landing page with cute pure CSS clouds. They randomly displace when hovered, and fly down from offscreen when you initially load the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexibly adding project pages using reusable React components&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TI2CnFKn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AWEU7la6oDL9wGA_gRfG1gw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TI2CnFKn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AWEU7la6oDL9wGA_gRfG1gw.png" alt="The project cards" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the best parts about the new website is that I have a single file, projects.json, which contains all of the information I need to build out project cards and project pages. This is achieved using React &lt;a href="https://reactjs.org/docs/components-and-props.html"&gt;components&lt;/a&gt;, where I load the static JSON file and pass its information as &lt;a href="https://reactjs.org/docs/components-and-props.html"&gt;props&lt;/a&gt; to the project page. If I complete a new project, I simply need to update the projects file with the new information and some pictures and it will automatically create a new page for it with no extra effort on my part!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The world’s easiest deployment with Vercel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dl2j_5Gi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AZ8S5Rs-sqs5ZeiNR-iaGmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dl2j_5Gi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AZ8S5Rs-sqs5ZeiNR-iaGmg.png" alt="Deployment dashboard" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com/solutions/nextjs"&gt;Vercel&lt;/a&gt;, the developer of Next.js, has made a cloud service that makes it incredibly quick to deploy, scale and iterate on Next.js projects with no need for configuration.&lt;/p&gt;

&lt;p&gt;The best part? Since it’s integrated with GitHub, any changes you make to the deployment branch of your GitHub repository will be automatically deployed to the sight! No hassle and no need to push the changes to Vercel.&lt;/p&gt;

&lt;p&gt;Even better, its completely free for hobby users! They provide a .vercel.app domain for any project you deploy, which can be easily replaced with your own custom domain from a service such as &lt;a href="https://www.namecheap.com/"&gt;Namecheap&lt;/a&gt;, &lt;a href="https://www.godaddy.com/"&gt;GoDaddy&lt;/a&gt;, or &lt;a href="https://domains.google/"&gt;Google Domains&lt;/a&gt;. I took the extra steps to use &lt;a href="https://www.cloudflare.com/"&gt;CloudFlare&lt;/a&gt; on top of Vercel, allowing me to easily see website statistics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kq3B67EU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ArwjHaSTqdXAKzidFpNo3yg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kq3B67EU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ArwjHaSTqdXAKzidFpNo3yg.png" alt="CloudFlare statistics for jordantwells.com" width="679" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s next for me?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s time to build out that portfolio! I’ll be working on plenty of projects in the coming months, so be sure to follow me to hear all the updates! And check out the finished website, &lt;a href="http://www.jordantwells.com"&gt;www.jordantwells.com&lt;/a&gt;, I’ll be keeping it up to date with all of my recent projects! And if you’re curious to see how I made it, check out my &lt;a href="https://github.com/jordantwells42"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>react</category>
    </item>
  </channel>
</rss>
