<?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: Michał Hawełka</title>
    <description>The latest articles on DEV Community by Michał Hawełka (@hwlkdev).</description>
    <link>https://dev.to/hwlkdev</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%2F612541%2F6ce4b30d-2f5e-4c87-bb95-5c5543dd605c.jpg</url>
      <title>DEV Community: Michał Hawełka</title>
      <link>https://dev.to/hwlkdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hwlkdev"/>
    <language>en</language>
    <item>
      <title>WeAreDevelopers World Congress - key takeaways</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Tue, 01 Aug 2023 06:01:52 +0000</pubDate>
      <link>https://dev.to/hwlkdev/wearedevelopers-world-congress-key-takeaways-556d</link>
      <guid>https://dev.to/hwlkdev/wearedevelopers-world-congress-key-takeaways-556d</guid>
      <description>&lt;p&gt;&lt;strong&gt;WeAreDevelopers World Congress is one of the biggest IT events in Europe. This year I got to be one of the participants. Two days packed with talks on a wide range of topics in IT. I attended mostly those revolving around frontend development, green IT and accessibility. Here’s a quick summary of what I saw.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FfOHPUgY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867481/WeAreDevs/WAD10_a2whsx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FfOHPUgY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867481/WeAreDevs/WAD10_a2whsx.jpg" alt="Congress entrance" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sir Tim Berners-Lee's keynote
&lt;/h3&gt;

&lt;p&gt;It started strong with a keynote by Sir Tim Berners-Lee, inventor of the World Wide Web. It was nice to hear his thoughts on the future of the internet - decentralized, safe and with data sovereignty, even though his ideas seem… a little bit utopian. I must say I took his words with some skepticism, but I get where he’s coming from. Great minds think big, so maybe my little brain is too overwhelmed with his ideas 😀&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fWWRYWeP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867480/WeAreDevs/WAD6_tosrsx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fWWRYWeP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867480/WeAreDevs/WAD6_tosrsx.jpg" alt="Main Stage" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;He focused on &lt;strong&gt;Solid API&lt;/strong&gt;, the web standard of the future. I must say - all of this sounds compelling and I’ll dig deeper into the topic. Maybe then I’ll understand the hype. For now - take a look at it yourselves:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://solidproject.org/"&gt;Solid Project&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fighting climate change with code
&lt;/h3&gt;

&lt;p&gt;The two most interesting talks on the first day were revolving around green coding. Both “Reducing the carbon footprint of your website” by Ines Akrap (fantastic talk, it’s a pity it was so short) and “GreenCoding - Let’s build sustainable software to fight climate change!” by Tim Schade gave me a pretty good idea where to start and how to make our software “green”. Especially Ines’ talk was inspiring to me as she showed what can we do from the frontend development perspective.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That’s great&lt;/strong&gt; - but what are the key takeaways from those talks?&lt;/p&gt;

&lt;p&gt;First of all, it’s important to understand that Digital is not equal to Green. When talking about something being green we must consider the electricity usage, but also the source of electricity, time of day or country in which it is produced as the emissions differ quite largely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/GrantChalmers/status/1532105847769530368"&gt;Check this visualization on Twitter X&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s a great article on calculating digital emissions on Sustainable Web Design’s website: &lt;a href="https://sustainablewebdesign.org/calculating-digital-emissions/"&gt;Calculating Digital Emissions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dtNmQPkp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867480/WeAreDevs/WAD5_rsfm6f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dtNmQPkp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867480/WeAreDevs/WAD5_rsfm6f.jpg" alt="Ines on stage 6" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And how to reduce the website’s carbon footprint? Ines gave quite a few tips to do that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;using “green” hosting - check where your hosting provider has its servers, what kind of electricity sources are they using etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;remove unused features&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;reduce the number of font files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;use images of the proper size&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;disable autoplay on the videos on your site and (if possible) use a custom clickable thumbnail instead of a video player - that would speed up loading for most of the people that won’t watch the video&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;use caching and CDNs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s just a few tips, but even those can greatly reduce your website’s carbon footprint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0TnfVxgw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867483/WeAreDevs/WAD3_v12pfz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0TnfVxgw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867483/WeAreDevs/WAD3_v12pfz.jpg" alt="Tim on Airstream Stage" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One question stands - why would you care? Your website is a drop in the sea, right? Well, even if one visit to your website generates several kg of CO2 - think how much it would generate after 1000, 10000 or even a million visits. Then the numbers start to become scary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Applied Psychology: Psychology-based UI improvements
&lt;/h3&gt;

&lt;p&gt;“Applied Psychology: Psychology-based UI improvements” by Keren Kenzi was a really fast talk on how psychology principles can make websites more compelling to the user. As it was only 10 minutes it was a brief introduction to the topic, but an interesting one. I’ll read about this more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking the bias in tech
&lt;/h3&gt;

&lt;p&gt;Anna John’s story about changing careers and becoming an engineering manager as a woman was inspiring. She gave us some tips on how to fight biases in Tech, both as a privileged person (straight white male, hello) and as a person in the opposite situation (pretty much everybody else unfortunately).&lt;/p&gt;

&lt;p&gt;The key takeaways for me are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Don’t be afraid to change careers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set a goal and use failures to your advantage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get better at saying no and setting boundaries&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BQT_gaMx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867480/WeAreDevs/WAD4_jvolue.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BQT_gaMx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867480/WeAreDevs/WAD4_jvolue.jpg" alt="Capgemini's stand" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Saving the world and making it a better place&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Hollie Gruber talked about our possibilities as developers to do something good. How to help non-profits using our skills? How to find a suitable beneficiary? What you can do? There is no simple answer to these questions, but you can make your search easier using one of the links that Hollie shared with us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.impactcloud.org/"&gt;Impact Cloud&lt;/a&gt; - a platform that helps connect non-profits with big companies that can provide their services free or at discount&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.nten.org/"&gt;NTEN&lt;/a&gt; - non-profit networking community&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.ffwd.org/"&gt;Fast Forward&lt;/a&gt; - a place where you can find top tech non-profits&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://goodtechfest.com/"&gt;Good Tech Fest&lt;/a&gt; - conference on doing good in tech (happening both online and in Washington, DC)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nethope.org/global-summit/"&gt;NetHope Global Summit&lt;/a&gt; - another meeting place where you can find out how technology can help with making this world a better place&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hollie also showed us the UN’s 17 goals for sustainable development. It was the first time I came to know about it and I encourage you to learn more about it here: &lt;a href="https://sdgs.un.org/goals"&gt;UN's Goals of Sustainable Development&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  It’s easy to create a good-looking product, but what about a useful one?
&lt;/h3&gt;

&lt;p&gt;This talk by Eleftheria Batsou was in a nutshell about the differences between UX and UI, UX principles and do’s and don’ts. Well put introductory stuff, a good start for someone that would like to dig deeper into the topic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MTcqwQ47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867480/WeAreDevs/WAD2_gxbxb2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MTcqwQ47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867480/WeAreDevs/WAD2_gxbxb2.jpg" alt="Eleftheria on Microstage 3" width="800" height="801"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most important takeaway from it though would be 10 usability heuristics for UI design - those were created by Jakob Nielsen and go as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Visibility of system status&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Match between the system and the real world&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User control and freedom&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistency and standards&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Error prevention&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recognition rather than recall&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flexibility and efficiency of use&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Aesthetic and minimal design&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Help users recognize, diagnose and recover from errors&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Help and documentation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are great as you don’t have to be a UI professional to make use of those guidelines. You can read more about it on &lt;a href="https://www.nngroup.com/articles/ten-usability-heuristics/"&gt;Nielsen Norman Group’s website&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility with Web Components
&lt;/h3&gt;

&lt;p&gt;Talk by Manuel Mauky. I’m going to be honest - Web Components are not my cup of tea. I never worked with them and I never really had any interest in them. But lately, I went through a course on Web Components on Frontend Masters and I kinda see the appeal to use them. What I didn’t see was that accessibility could be an issue. Manuel’s talk not only made me aware of this but also provided me with solutions. Cool stuff!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g9O6FKci--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867480/WeAreDevs/WAD1_bwimd6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g9O6FKci--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867480/WeAreDevs/WAD1_bwimd6.jpg" alt="Manuel on Stage 6" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  From zero to hero: NextJS 13 and TailwindCSS for Beginners
&lt;/h3&gt;

&lt;p&gt;Talk by Marius Obert. I know, I know, NextJS and TailwindCSS are all the rage now. I also jumped on that ship and I must say I like it. Both technologies (frameworks? libraries?) are really fun to work with. So why did I go to the talk that has “beginners” in its name? Well, NextJS 13 is pretty new - I wanted to get the view from the most modern perspective. And even though most of the stuff presented wasn’t new to me I still had a great time during the talk. Kudos to Marius!&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth it?
&lt;/h2&gt;

&lt;p&gt;100%. Two days of inspiring talks and networking. I liked it and would recommend it if you have the option to go there next year. If you are an IT professional you’ll find something interesting there. So, see you next year in Berlin?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t72MElTK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867481/WeAreDevs/WAD9_dpuwh3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t72MElTK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_scale%2Cq_auto:eco%2Cw_1000/v1690867481/WeAreDevs/WAD9_dpuwh3.jpg" alt="WAD" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>conference</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>How to create accessible buttons in HTML</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Wed, 05 Jul 2023 08:59:45 +0000</pubDate>
      <link>https://dev.to/hwlkdev/how-to-create-accessible-buttons-in-html-5g4p</link>
      <guid>https://dev.to/hwlkdev/how-to-create-accessible-buttons-in-html-5g4p</guid>
      <description>&lt;p&gt;Accessibility was always important, but lately it gained even more traction and if you don’t know how to create accessible websites, you can’t really call yourself a good frontend developer. That’s a bold statement, I know, but it’s true, just deal with it. &lt;/p&gt;

&lt;p&gt;Today we’ll look at one of the most basic elements of any website - a button. How to make it accessible? Without further ado - let’s just jump into it.&lt;/p&gt;

&lt;p&gt;First and foremost - use a &lt;code&gt;button&lt;/code&gt; tag. Why? Why not a &lt;code&gt;div&lt;/code&gt; for example? Here you have a few reasons &lt;code&gt;button&lt;/code&gt; tag is the better choice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It’s semantically correct.&lt;/strong&gt; Semantic HTML is really important, it helps with code readability and also helps web crawlers better understand our website.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It’s focusable and clickable by default.&lt;/strong&gt; You don’t need to implement those behaviours (but if you use some CSS frameworks that reset styles for every element - remember to implement &lt;code&gt;hover&lt;/code&gt; and &lt;code&gt;focus&lt;/code&gt; states)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It’s recognisable by screen readers.&lt;/strong&gt; When you use any of the screen readers it will announce the &lt;code&gt;button&lt;/code&gt; tag as, well, a button.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Is that it? That would be great, wouldn’t it?&lt;br&gt;
But no, next thing we need to talk about is button content.  Here we can go three ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Text&lt;/li&gt;
&lt;li&gt;Icon + text&lt;/li&gt;
&lt;li&gt;Only Icon&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Text buttons
&lt;/h3&gt;

&lt;p&gt;In this case the best thing we can do is to just use the text that is self explanatory. We don’t need to put any additional attributes to make this kind of button accessible.&lt;br&gt;
There can be occasions where you’d be tempted to use &lt;code&gt;aria-label&lt;/code&gt; to “overwrite” the text on the button. I strongly recommend you not to do it - it is against one of the WCAG principles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You can check all the code that I use in this article here: &lt;a href="https://codepen.io/hwlkdev/pen/zYLbJJK"&gt;Codepen&lt;/a&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&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;body&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="p"&gt;&amp;gt;&lt;/span&gt;This is a test button&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="err"&gt;!--&lt;/span&gt; &lt;span class="na"&gt;incorrect&lt;/span&gt; &lt;span class="err"&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;button&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"This is a different button"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;This is another button&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;body&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;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Icon with text buttons
&lt;/h3&gt;

&lt;p&gt;The second case - icon with text is also quite simple. We can actually use the same technique as for the simple text, but with one additional thing - we’ll need to add &lt;code&gt;aria-hidden=true&lt;/code&gt; attribute for the &lt;code&gt;svg&lt;/code&gt; icon. This way screen readers will ignore the icon and read only the text. &lt;/p&gt;

&lt;p&gt;One thing also worth doing is to add &lt;code&gt;focusable=false&lt;/code&gt; attribute as some of the old browsers focus on &lt;code&gt;svg&lt;/code&gt; for some reason.&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="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;svg&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"svg-icon"&lt;/span&gt; &lt;span class="na"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0 0 20 20"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"false"&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;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;The icon&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&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;path&lt;/span&gt; &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"M9.719,17.073l-6.562-6.51c-0.27-0.268-0.504-0.567-0.696-0.888C1.385,7.89,1.67,5.613,3.155,4.14c0.864-0.856,2.012-1.329,3.233-1.329c1.924,0,3.115,1.12,3.612,1.752c0.499-0.634,1.689-1.752,3.612-1.752c1.221,0,2.369,0.472,3.233,1.329c1.484,1.473,1.771,3.75,0.693,5.537c-0.19,0.32-0.425,0.618-0.695,0.887l-6.562,6.51C10.125,17.229,9.875,17.229,9.719,17.073 M6.388,3.61C5.379,3.61,4.431,4,3.717,4.707C2.495,5.92,2.259,7.794,3.145,9.265c0.158,0.265,0.351,0.51,0.574,0.731L10,16.228l6.281-6.232c0.224-0.221,0.416-0.466,0.573-0.729c0.887-1.472,0.651-3.346-0.571-4.56C15.57,4,14.621,3.61,13.612,3.61c-1.43,0-2.639,0.786-3.268,1.863c-0.154,0.264-0.536,0.264-0.69,0C9.029,4.397,7.82,3.61,6.388,3.61"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;path&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;svg&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Button with icon
&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Icon buttons
&lt;/h3&gt;

&lt;p&gt;The third case - icon only button is also pretty straightforward. We can just use &lt;code&gt;aria-label&lt;/code&gt; on the &lt;code&gt;button&lt;/code&gt; tag and the screen readers will ignore everything that’s inside the button (so in this case - an icon). We don’t even need to add &lt;code&gt;aria-hidden=true&lt;/code&gt; on the icon.&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Like"&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;svg&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"svg-icon"&lt;/span&gt; &lt;span class="na"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0 0 20 20"&lt;/span&gt; &lt;span class="na"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"40px"&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;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;The icon&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&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;path&lt;/span&gt; &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"M9.719,17.073l-6.562-6.51c-0.27-0.268-0.504-0.567-0.696-0.888C1.385,7.89,1.67,5.613,3.155,4.14c0.864-0.856,2.012-1.329,3.233-1.329c1.924,0,3.115,1.12,3.612,1.752c0.499-0.634,1.689-1.752,3.612-1.752c1.221,0,2.369,0.472,3.233,1.329c1.484,1.473,1.771,3.75,0.693,5.537c-0.19,0.32-0.425,0.618-0.695,0.887l-6.562,6.51C10.125,17.229,9.875,17.229,9.719,17.073 M6.388,3.61C5.379,3.61,4.431,4,3.717,4.707C2.495,5.92,2.259,7.794,3.145,9.265c0.158,0.265,0.351,0.51,0.574,0.731L10,16.228l6.281-6.232c0.224-0.221,0.416-0.466,0.573-0.729c0.887-1.472,0.651-3.346-0.571-4.56C15.57,4,14.621,3.61,13.612,3.61c-1.43,0-2.639,0.786-3.268,1.863c-0.154,0.264-0.536,0.264-0.69,0C9.029,4.397,7.82,3.61,6.388,3.61"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;path&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;svg&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="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is definitely the simplest and most supported solution. But what are the alternatives?&lt;/p&gt;

&lt;p&gt;First one would be creating a &lt;code&gt;sr-only&lt;/code&gt; or &lt;code&gt;visuallyhidden&lt;/code&gt; class in your CSS and then using to create an “invisible” label. TailwindCSS defines &lt;code&gt;sr-only&lt;/code&gt; class as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.sr-only&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&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;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;When you have this class in place you can then use it 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="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;svg&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"svg-icon"&lt;/span&gt; &lt;span class="na"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0 0 20 20"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"40px"&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;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;The icon&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&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;path&lt;/span&gt; &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"M9.719,17.073l-6.562-6.51c-0.27-0.268-0.504-0.567-0.696-0.888C1.385,7.89,1.67,5.613,3.155,4.14c0.864-0.856,2.012-1.329,3.233-1.329c1.924,0,3.115,1.12,3.612,1.752c0.499-0.634,1.689-1.752,3.612-1.752c1.221,0,2.369,0.472,3.233,1.329c1.484,1.473,1.771,3.75,0.693,5.537c-0.19,0.32-0.425,0.618-0.695,0.887l-6.562,6.51C10.125,17.229,9.875,17.229,9.719,17.073 M6.388,3.61C5.379,3.61,4.431,4,3.717,4.707C2.495,5.92,2.259,7.794,3.145,9.265c0.158,0.265,0.351,0.51,0.574,0.731L10,16.228l6.281-6.232c0.224-0.221,0.416-0.466,0.573-0.729c0.887-1.472,0.651-3.346-0.571-4.56C15.57,4,14.621,3.61,13.612,3.61c-1.43,0-2.639,0.786-3.268,1.863c-0.154,0.264-0.536,0.264-0.69,0C9.029,4.397,7.82,3.61,6.388,3.61"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;path&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;svg&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;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Like&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;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And as you can see and hear - it works as expected (I strongly recommend you to try focusing on the buttons with VoiceOver turned on - it really helps understanding the things that we are doing here).&lt;/p&gt;

&lt;p&gt;Another solution is a little bit more complicated. We need to use &lt;code&gt;aria-labelledby&lt;/code&gt; on the button and then provide a &lt;code&gt;hidden&lt;/code&gt; label inside. Be aware that even though the label will be hidden, &lt;code&gt;labelledby&lt;/code&gt; attribute will still be able to access it. &lt;/p&gt;

&lt;p&gt;It would 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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"likeBtn"&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;svg&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"svg-icon"&lt;/span&gt; &lt;span class="na"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0 0 20 20"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"40px"&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;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;The icon&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&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;path&lt;/span&gt; &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"M9.719,17.073l-6.562-6.51c-0.27-0.268-0.504-0.567-0.696-0.888C1.385,7.89,1.67,5.613,3.155,4.14c0.864-0.856,2.012-1.329,3.233-1.329c1.924,0,3.115,1.12,3.612,1.752c0.499-0.634,1.689-1.752,3.612-1.752c1.221,0,2.369,0.472,3.233,1.329c1.484,1.473,1.771,3.75,0.693,5.537c-0.19,0.32-0.425,0.618-0.695,0.887l-6.562,6.51C10.125,17.229,9.875,17.229,9.719,17.073 M6.388,3.61C5.379,3.61,4.431,4,3.717,4.707C2.495,5.92,2.259,7.794,3.145,9.265c0.158,0.265,0.351,0.51,0.574,0.731L10,16.228l6.281-6.232c0.224-0.221,0.416-0.466,0.573-0.729c0.887-1.472,0.651-3.346-0.571-4.56C15.57,4,14.621,3.61,13.612,3.61c-1.43,0-2.639,0.786-3.268,1.863c-0.154,0.264-0.536,0.264-0.69,0C9.029,4.397,7.82,3.61,6.388,3.61"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;path&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;svg&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;span&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"likeBtn"&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Like&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;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting the &lt;code&gt;id&lt;/code&gt; on the &lt;code&gt;span&lt;/code&gt; is important because that’s how we can access the label from the &lt;code&gt;labelledby&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;All three solutions work the same. That’s why my favourite is just setting the &lt;code&gt;aria-label&lt;/code&gt;, it the simplest solution.&lt;/p&gt;

&lt;p&gt;These were the most important accessibility adjustments you can make to ensure your buttons work well with screen readers. But what about people with visual impairments that do not use screen readers? Here we have two additional things to take care about and we’ll be all set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sizing
&lt;/h3&gt;

&lt;p&gt;First let’s talk about sizing. In the WCAG guidelines there is a criterion 2.5.5 - Target Size. It’s described as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The size of the target for pointer inputs is at least 44 by 44 CSS pixels.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are few exceptions and you can see those on the screen now, they are not relevant in this specific case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OelCD5wn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_pad%2Cb_auto:predominant%2Cfl_preserve_transparency/v1688538894/A11Y_exceptions_zvrpbt.jpg%3F_s%3Dpublic-apps" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OelCD5wn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_pad%2Cb_auto:predominant%2Cfl_preserve_transparency/v1688538894/A11Y_exceptions_zvrpbt.jpg%3F_s%3Dpublic-apps" alt="A11Y" width="800" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So let’s ensure that our buttons are in fact big enough. I’m not a fan of using pixels, I’d much rather go with &lt;code&gt;rems&lt;/code&gt; or &lt;code&gt;ems&lt;/code&gt;. This will also help if the user changes his base font size - the button will still be sized properly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3rem&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;I used &lt;code&gt;3rem&lt;/code&gt; as usually the root font size is &lt;code&gt;16px&lt;/code&gt;, so &lt;code&gt;3rem&lt;/code&gt; gives us &lt;code&gt;48px&lt;/code&gt;. But if the user zooms in to, let’s say, &lt;code&gt;24px&lt;/code&gt;, the button will be larger.&lt;/p&gt;

&lt;p&gt;Ok, with that out of the way - let’s handle the last thing I want to talk about today - contrast. &lt;/p&gt;

&lt;h3&gt;
  
  
  Contrast
&lt;/h3&gt;

&lt;p&gt;WCAG guidelines say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The visual presentation of text and images of text has a contrast ratio of at least 4.5:1 - for AA success criterion, and 7:1 for AAA criterion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The best tool to use here is contrast checker by WebAIM &lt;a href="https://webaim.org/resources/contrastchecker/"&gt;https://webaim.org/resources/contrastchecker/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can just put your button color there and you’ll get the text color and vice versa. Simple, yet great tool. In the case of my examples let’s say I wanted a green buttons.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1sYpiDc9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_pad%2Cb_auto:predominant%2Cfl_preserve_transparency/v1688538894/ContrastChecker_zhvjrv.jpg%3F_s%3Dpublic-apps" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1sYpiDc9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/c_pad%2Cb_auto:predominant%2Cfl_preserve_transparency/v1688538894/ContrastChecker_zhvjrv.jpg%3F_s%3Dpublic-apps" alt="Contrast Checker" width="800" height="863"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see white text fails all of the criteria, but black should be OK. As you can see while I’m scrolling through the colors the contrast ratio changes and also different criteria are passing. With straight black we get a contrast of over 10 to 1. So that’s going to work!&lt;/p&gt;

&lt;p&gt;And that’s it! Those are the things that you can do to make your buttons accessible. I hope you liked it, if you did - consider following me on Twitter, Dev.to and LinkedIn, more frontend and accessibility related content is on the way.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>SEO handling in Strapi [Building Personal Blog Website Part 7]</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Mon, 12 Dec 2022 14:55:50 +0000</pubDate>
      <link>https://dev.to/hwlkdev/seo-handling-in-strapi-building-personal-blog-website-part-7-8hm</link>
      <guid>https://dev.to/hwlkdev/seo-handling-in-strapi-building-personal-blog-website-part-7-8hm</guid>
      <description>&lt;p&gt;There are two approaches for SEO in Strapi - you can create a SEO component by hand or use a SEO plugin which provides an extensive component. In this guide you’ll use the plugin, but you’ll edit the component a bit so it has only the relevant (for us) data.&lt;/p&gt;

&lt;p&gt;Open your Strapi repository and install SEO plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add @strapi/plugin-seo
yarn build
yarn develop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now log in to admin panel locally and click on the SEO panel on the left hand side - this should initialize the plugin:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg092k3oep93vm5nqziym.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg092k3oep93vm5nqziym.jpg" alt="Blog-7-1.jpg" width="241" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go back to Content-type builder - there should be two new components created:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96vp9udgsf9ri3cuz50e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96vp9udgsf9ri3cuz50e.jpg" alt="Blog-7-2.jpg" width="210" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll use only SEO one, you’ll get rid of the &lt;code&gt;MetaSocial&lt;/code&gt; component. Why? This component assumes you’ll use a different image, title and description for each social media, while in reality the title and description will stay the same and the image used for Facebook and LinkedIn would be the same. Only for Twitter you’d need another image - and you’ll make the change to the SEO component so it allows you to upload this as well. Start with deleting &lt;code&gt;metaSocial&lt;/code&gt; from &lt;code&gt;Seo&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0cwfxo3zp2dcz9vheb5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0cwfxo3zp2dcz9vheb5.jpg" alt="Blog-7-3.jpg" width="767" height="847"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now add a new field for &lt;code&gt;metaTwitterImage&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie0g1twywmbvc2udiy4d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie0g1twywmbvc2udiy4d.jpg" alt="Blog-7-4.jpg" width="723" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And just to keep your CMS clean - Edit &lt;code&gt;MetaSocial&lt;/code&gt; component and press Delete - you’ll not need it anymore.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbg0d7iguuqre5ge3w3z.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbg0d7iguuqre5ge3w3z.jpg" alt="Blog-7-5.jpg" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s time to add &lt;code&gt;Seo&lt;/code&gt; component to your Posts. Still in Content-type builder open Post collection type and add another field:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qg9zccoy9typlfn9dh8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qg9zccoy9typlfn9dh8.jpg" alt="Blog-7-6.jpg" width="800" height="699"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;em&gt;Component&lt;/em&gt; and &lt;em&gt;Use an existing component&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkba7odt3u7y4494cclaf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkba7odt3u7y4494cclaf.jpg" alt="Blog-7-7.jpg" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now select &lt;code&gt;seo&lt;/code&gt; component, put a name (preferrably &lt;code&gt;seo&lt;/code&gt;) and select &lt;em&gt;Single component&lt;/em&gt;. Then save the Post model and you’ll be set.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjab7x6s3sqdmyrmg1sej.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjab7x6s3sqdmyrmg1sej.jpg" alt="Blog-7-8.jpg" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now in VSCode (or Terminal) commit all the files and publish changes to GitHub and Railway.&lt;/p&gt;

&lt;p&gt;When it rebuilds, log in to admin panel on your hosted Strapi. Go to the content manager, open any of your posts and there you should be able to add SEO. Provide &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;metaImage&lt;/code&gt; (preferably 1200x630 since this is a fitting size for both Facebook and LinkedIn) and &lt;code&gt;metaTwitterImage&lt;/code&gt; (here use 560x300). If you’d like - provide SEO for all your posts. If you’re done - you can go to VSCode and open your Next.js app.&lt;/p&gt;

&lt;p&gt;You need to do three things here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update GraphQL query in &lt;code&gt;[slug].js&lt;/code&gt; so it fetch also SEO data.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;next/head&lt;/code&gt; to add proper meta tags&lt;/li&gt;
&lt;li&gt;Validate if SEO data actually exists - SEO is optional in your CMS. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s start with the first task. Open &lt;code&gt;[slug].js&lt;/code&gt; and in GraphQL query in &lt;code&gt;getStaticProps&lt;/code&gt; method add the following fields to be fetched:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SEO &lt;span class="o"&gt;{&lt;/span&gt;
  metaTitle
  metaDescription
  metaImage &lt;span class="o"&gt;{&lt;/span&gt;
    data &lt;span class="o"&gt;{&lt;/span&gt;
      attributes &lt;span class="o"&gt;{&lt;/span&gt;
        url
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
  metaTwitterImage &lt;span class="o"&gt;{&lt;/span&gt;
    data &lt;span class="o"&gt;{&lt;/span&gt;
      attributes &lt;span class="o"&gt;{&lt;/span&gt;
        url
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it’s time to use the SEO data that you have. Update your Post component as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;default &lt;span class="k"&gt;function &lt;/span&gt;Post&lt;span class="o"&gt;({&lt;/span&gt; postData &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;&lt;span class="o"&gt;{&lt;/span&gt;postData.attributes.title&lt;span class="o"&gt;}&lt;/span&gt;&amp;lt;/title&amp;gt;
        &lt;span class="o"&gt;{&lt;/span&gt;postData.attributes.SEO &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
          &amp;lt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &amp;lt;meta
              &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"description"&lt;/span&gt;
              &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;postData.attributes.SEO.metaDescription&lt;span class="o"&gt;}&lt;/span&gt;
            /&amp;gt;
            &amp;lt;meta &lt;span class="nv"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"og:title"&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;postData.attributes.title&lt;span class="o"&gt;}&lt;/span&gt; /&amp;gt;
            &amp;lt;meta &lt;span class="nv"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"og:type"&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"article"&lt;/span&gt; /&amp;gt;
            &amp;lt;meta
              &lt;span class="nv"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"og:image"&lt;/span&gt;
              &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;postData.attributes.SEO.metaImage.data.attributes.url&lt;span class="o"&gt;}&lt;/span&gt;
            /&amp;gt;
            &amp;lt;meta
              &lt;span class="nv"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"twitter:image"&lt;/span&gt;
              &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;
                postData.attributes.SEO.metaTwitterImage.data.attributes.url
              &lt;span class="o"&gt;}&lt;/span&gt;
            /&amp;gt;
            &amp;lt;meta &lt;span class="nv"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"twitter:card"&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"summary_large_image"&lt;/span&gt; /&amp;gt;
          &amp;lt;/&amp;gt;
        &lt;span class="o"&gt;)}&lt;/span&gt;
      &amp;lt;/Head&amp;gt;
      &amp;lt;ReactMarkdown &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"prose prose-sm md:prose-lg lg:prose-xl xl:prose-2xl prose-slate w-11/12 mx-4 my-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;postData.attributes.content&lt;span class="o"&gt;}&lt;/span&gt;
      &amp;lt;/ReactMarkdown&amp;gt;
    &amp;lt;/&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;{postData.attributes.SEO &amp;amp;&amp;amp; (&lt;/code&gt; ensures that you’ll render meta tags only when the SEO data is actually provided. You should aim to provide it for every post as this not only helps with Google ranking, but also with properly displaying the relevant data while sharing posts on social media.&lt;/p&gt;

&lt;p&gt;When that’s done rebuild your app locally to check if everything is OK. If it is - commit your changes and push them to GitHub. When the app rebuilds on Netlify open any of the posts that you provided SEO data for. First of all you should notice that on the browser bar there is an actual title of the post. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g7b8w9yvbbq5k1tdzzc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g7b8w9yvbbq5k1tdzzc.jpg" alt="Blog-7-9.jpg" width="642" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the rest of the metadata you’ll need to go to &lt;a href="https://developers.facebook.com/tools/debug/" rel="noopener noreferrer"&gt;https://developers.facebook.com/tools/debug/&lt;/a&gt; and enter URL to your post and click Debug:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1t59tqr6yxk9jq4ucstp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1t59tqr6yxk9jq4ucstp.jpg" alt="Blog-7-10.jpg" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should get a similar result. You see the link preview - everything looks fine. There are also warnings that you are missing &lt;code&gt;og:url&lt;/code&gt; and &lt;code&gt;og:description&lt;/code&gt; metatags - add those on your own if you want. Let this be my challenge for you :)&lt;/p&gt;

&lt;p&gt;One more thing while you’re at it. Add some meta tags to your &lt;code&gt;_app.js&lt;/code&gt; file - this will ensure that every page on your site has at least the basic metadata in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;MyApp&lt;span class="o"&gt;({&lt;/span&gt; Component, pageProps &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex flex-col items-center bg-white"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;meta &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"UTF-8"&lt;/span&gt; /&amp;gt;
        &amp;lt;meta &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"author"&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR NAME"&lt;/span&gt; /&amp;gt;
        &amp;lt;meta &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"viewport"&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; /&amp;gt;
      &amp;lt;/Head&amp;gt;
      &amp;lt;Navbar /&amp;gt;
      &amp;lt;Component &lt;span class="o"&gt;{&lt;/span&gt;...pageProps&lt;span class="o"&gt;}&lt;/span&gt; /&amp;gt;
      &amp;lt;Footer /&amp;gt;
    &amp;lt;/div&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Charset, author and viewport are a nice addition to your SEO. Title and description should be provided per page so just use the snippet below and add it to all of the pages(&lt;code&gt;[slug].js&lt;/code&gt;, &lt;code&gt;index.js&lt;/code&gt; etc.) -  you can also extract it to a separate component and use it this way - your choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;Head&amp;gt;
  &amp;lt;title&amp;gt;YOUR TITLE&amp;lt;/title&amp;gt;
  &amp;lt;meta &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"description"&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_DESCRIPTION"&lt;/span&gt; /&amp;gt;
&amp;lt;/Head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s all! Now you have a basic SEO provided on your website. In the next part we’ll dive deep into pagination, topic which will be extremely useful when the blog post count grows.&lt;/p&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Search functionality [Building Personal Blog Website Part 6]</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Mon, 05 Dec 2022 06:15:28 +0000</pubDate>
      <link>https://dev.to/hwlkdev/search-functionality-building-personal-blog-website-part-6-19gm</link>
      <guid>https://dev.to/hwlkdev/search-functionality-building-personal-blog-website-part-6-19gm</guid>
      <description>&lt;p&gt;Now you’ll use the experience from displaying list of posts for specific tag to create a Search component. User will be able to search for any query and should get a list of posts that fulfill the query.&lt;/p&gt;

&lt;p&gt;Start with creating new route pages/search/[query].js - it will be mostly based on [tag].js but with a twist! This time you will not use getStaticPaths as it is not possible to generate all queries that the user can put in the search bar. You’ll need to fetch results dynamically from the client.&lt;/p&gt;

&lt;p&gt;And as you’ll be fetching posts dynamically you need to ensure the user is aware that something is loading. Usually there are two ways of indicating this to the user visually - spinners or progress bars and skeleton content. The first solution is easier, but does not prepare the user for what’s coming and also it has some effects on a performance metric called Content Layout Shift (you can read more about it &lt;a href="https://web.dev/cls/" rel="noopener noreferrer"&gt;HERE&lt;/a&gt;). The second solution requires a bit more work, but it’s worth it - you’ll need to prepare a blank template of the loaded content to indicate to the user that something is loading. The end result will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p2rd4vl4rewl7x63fhj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p2rd4vl4rewl7x63fhj.jpg" alt="Blog-6-1.jpg" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So before you’ll implement [query].js let’s just quickly prepare this skeleton Blog Post preview. Create a new component components/BlogPostPreviewSkeleton.jsx and put this code inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;import React from &lt;span class="s2"&gt;"react"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

const BlogPostPreviewSkeleton &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&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;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;div
      &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;
      &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"space-y-8 animate-pulse md:space-y-0 md:space-x-8"&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex flex-col justify-between max-w-sm rounded overflow-hidden shadow-lg"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex justify-center items-center w-full h-48 bg-gray-300 rounded sm:w-96 dark:bg-gray-700"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &amp;lt;svg
            &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-12 h-12 text-gray-200"&lt;/span&gt;
            &lt;span class="nv"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;
            aria-hidden&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;
            &lt;span class="nv"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"currentColor"&lt;/span&gt;
            &lt;span class="nv"&gt;viewBox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 0 640 512"&lt;/span&gt;
          &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &amp;lt;path &lt;span class="nv"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"M480 80C480 35.82 515.8 0 560 0C604.2 0 640 35.82 640 80C640 124.2 604.2 160 560 160C515.8 160 480 124.2 480 80zM0 456.1C0 445.6 2.964 435.3 8.551 426.4L225.3 81.01C231.9 70.42 243.5 64 256 64C268.5 64 280.1 70.42 286.8 81.01L412.7 281.7L460.9 202.7C464.1 196.1 472.2 192 480 192C487.8 192 495 196.1 499.1 202.7L631.1 419.1C636.9 428.6 640 439.7 640 450.9C640 484.6 612.6 512 578.9 512H55.91C25.03 512 .0006 486.1 .0006 456.1L0 456.1z"&lt;/span&gt; /&amp;gt;
          &amp;lt;/svg&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"px-6 py-4"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;lt;/div&amp;gt;
          &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[480px] mb-2.5"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;lt;/div&amp;gt;
          &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;lt;/div&amp;gt;
          &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[440px] mb-2.5"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;lt;/div&amp;gt;
          &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[460px] mb-2.5"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;lt;/div&amp;gt;
          &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"px-6 pt-4 pb-2"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;span &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sr-only"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;Loading...&amp;lt;/span&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;default BlogPostPreviewSkeleton&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used the code from &lt;a href="https://flowbite.com/docs/components/skeleton/" rel="noopener noreferrer"&gt;Flowbite&lt;/a&gt;, but adjusted it to my needs (so the layout looks kinda like BlogPostPreview component). As you can see there’s also a span for screen readers (it will be read instead of all the other content that is here).&lt;/p&gt;

&lt;p&gt;The next step would be implementing actual Search Results page. In your [query].js put this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;import &lt;span class="o"&gt;{&lt;/span&gt; gql &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s2"&gt;"@apollo/client"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import &lt;span class="o"&gt;{&lt;/span&gt; useRouter &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s2"&gt;"next/router"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import &lt;span class="o"&gt;{&lt;/span&gt; useEffect, useState &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s2"&gt;"react"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import client from &lt;span class="s2"&gt;"../../apollo-client"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import BlogPostPreview from &lt;span class="s2"&gt;"../../components/BlogPostPreview"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import BlogPostPreviewSkeleton from &lt;span class="s2"&gt;"../../components/BlogPostPreviewSkeleton"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;default &lt;span class="k"&gt;function &lt;/span&gt;SearchResults&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  const router &lt;span class="o"&gt;=&lt;/span&gt; useRouter&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  const &lt;span class="o"&gt;{&lt;/span&gt; query &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; router.query&lt;span class="p"&gt;;&lt;/span&gt;
  const &lt;span class="o"&gt;[&lt;/span&gt;searchResults, setSearchResults] &lt;span class="o"&gt;=&lt;/span&gt; useState&lt;span class="o"&gt;(&lt;/span&gt;null&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  useEffect&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    const getSearchResults &lt;span class="o"&gt;=&lt;/span&gt; async &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      const &lt;span class="o"&gt;{&lt;/span&gt; data &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; await client.query&lt;span class="o"&gt;({&lt;/span&gt;
        query: gql&lt;span class="sb"&gt;`&lt;/span&gt;
          query Posts &lt;span class="o"&gt;{&lt;/span&gt;
            posts&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sort&lt;/span&gt;: &lt;span class="s2"&gt;"publishedAt:desc"&lt;/span&gt;
                filters: &lt;span class="o"&gt;{&lt;/span&gt; content: &lt;span class="o"&gt;{&lt;/span&gt; containsi: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
              data &lt;span class="o"&gt;{&lt;/span&gt;
                attributes &lt;span class="o"&gt;{&lt;/span&gt;
                  title
                  slug
                  tags &lt;span class="o"&gt;{&lt;/span&gt;
                    data &lt;span class="o"&gt;{&lt;/span&gt;
                      attributes &lt;span class="o"&gt;{&lt;/span&gt;
                        tagId
                        name
                      &lt;span class="o"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt;
                  &lt;span class="o"&gt;}&lt;/span&gt;
                  publishedAt
                  excerpt
                  cover &lt;span class="o"&gt;{&lt;/span&gt;
                    data &lt;span class="o"&gt;{&lt;/span&gt;
                      attributes &lt;span class="o"&gt;{&lt;/span&gt;
                        url
                      &lt;span class="o"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt;
                  &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
              &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="sb"&gt;`&lt;/span&gt;,
      &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;setSearchResults&lt;span class="o"&gt;(&lt;/span&gt;data.posts.data&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    getSearchResults&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      setSearchResults&lt;span class="o"&gt;(&lt;/span&gt;null&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;[&lt;/span&gt;query]&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  const preparePostPreviews &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&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;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;searchResults.length &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 0&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;searchResults.map&lt;span class="o"&gt;((&lt;/span&gt;post&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
        &amp;lt;BlogPostPreview &lt;span class="nv"&gt;post&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;post&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;post.attributes.slug&lt;span class="o"&gt;}&lt;/span&gt; /&amp;gt;
      &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
        &amp;lt;h4 &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"font-mono text-black text-lg sm:col-span-2 lg:col-span-3 text-center"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          No results
        &amp;lt;/h4&amp;gt;
      &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;section &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-8 mx-4"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;h2 &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"font-mono text-black text-xl md:text-4xl text-center mb-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        Search results &lt;span class="k"&gt;for&lt;/span&gt;: &amp;amp;quot&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;query&lt;span class="o"&gt;}&lt;/span&gt;&amp;amp;quot&lt;span class="p"&gt;;&lt;/span&gt;
      &amp;lt;/h2&amp;gt;
      &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 "&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;searchResults ? &lt;span class="o"&gt;(&lt;/span&gt;
          preparePostPreviews&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;)&lt;/span&gt; : &lt;span class="o"&gt;(&lt;/span&gt;
          &amp;lt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &amp;lt;BlogPostPreviewSkeleton /&amp;gt;
            &amp;lt;BlogPostPreviewSkeleton /&amp;gt;
            &amp;lt;BlogPostPreviewSkeleton /&amp;gt;
          &amp;lt;/&amp;gt;
        &lt;span class="o"&gt;)}&lt;/span&gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s go through this code together bit by bit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;const router &lt;span class="o"&gt;=&lt;/span&gt; useRouter&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
const &lt;span class="o"&gt;{&lt;/span&gt; query &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; router.query&lt;span class="p"&gt;;&lt;/span&gt;
const &lt;span class="o"&gt;[&lt;/span&gt;searchResults, setSearchResults] &lt;span class="o"&gt;=&lt;/span&gt; useState&lt;span class="o"&gt;(&lt;/span&gt;null&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First things to put in your newly created component are some useful declarations. &lt;code&gt;router&lt;/code&gt; and &lt;code&gt;query&lt;/code&gt; will be needed to properly extract search query from the URL. &lt;code&gt;useState&lt;/code&gt; for &lt;code&gt;searchResults&lt;/code&gt; is a internal component state keeping all the search results data inside. Initialize it with &lt;code&gt;null&lt;/code&gt; as at the beginning you don’t have the results yet.&lt;/p&gt;

&lt;p&gt;Now let’s look at the useEffect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;useEffect&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    const getSearchResults &lt;span class="o"&gt;=&lt;/span&gt; async &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      const &lt;span class="o"&gt;{&lt;/span&gt; data &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; await client.query&lt;span class="o"&gt;({&lt;/span&gt;
        query: gql&lt;span class="sb"&gt;`&lt;/span&gt;
          query Posts &lt;span class="o"&gt;{&lt;/span&gt;
            posts&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sort&lt;/span&gt;: &lt;span class="s2"&gt;"publishedAt:desc"&lt;/span&gt;
                filters: &lt;span class="o"&gt;{&lt;/span&gt; content: &lt;span class="o"&gt;{&lt;/span&gt; containsi: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
              data &lt;span class="o"&gt;{&lt;/span&gt;
                attributes &lt;span class="o"&gt;{&lt;/span&gt;
                  title
                  slug
                  tags &lt;span class="o"&gt;{&lt;/span&gt;
                    data &lt;span class="o"&gt;{&lt;/span&gt;
                      attributes &lt;span class="o"&gt;{&lt;/span&gt;
                        tagId
                        name
                      &lt;span class="o"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt;
                  &lt;span class="o"&gt;}&lt;/span&gt;
                  publishedAt
                  excerpt
                  cover &lt;span class="o"&gt;{&lt;/span&gt;
                    data &lt;span class="o"&gt;{&lt;/span&gt;
                      attributes &lt;span class="o"&gt;{&lt;/span&gt;
                        url
                      &lt;span class="o"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt;
                  &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
              &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="sb"&gt;`&lt;/span&gt;,
      &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;setSearchResults&lt;span class="o"&gt;(&lt;/span&gt;data.posts.data&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    getSearchResults&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      setSearchResults&lt;span class="o"&gt;(&lt;/span&gt;null&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;[&lt;/span&gt;query]&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the beginning you are fetching the data from &lt;em&gt;GraphQL API&lt;/em&gt;. It’s a rather standard call, but one thing is worth noting - the &lt;code&gt;containsi&lt;/code&gt; filter. &lt;code&gt;containsi&lt;/code&gt; filter matches the query against the selected field but it does it case insensitive. We put this &lt;em&gt;GraphQL&lt;/em&gt; call inside a local async function to easily call it in the body of the &lt;code&gt;useEffect&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;There’s also &lt;code&gt;setSearchResult(null)&lt;/code&gt; on cleanup - when the user performs another search while on the search results page, the component will first unmount and clear the previous results and then mount again with new data. And of course in the dependency array you have &lt;code&gt;query&lt;/code&gt; - you want to reload the data as soon as the &lt;code&gt;query&lt;/code&gt; changes.&lt;/p&gt;

&lt;p&gt;Later in the file you have this helper function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;const preparePostPreviews &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&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;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;searchResults.length &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 0&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;searchResults.map&lt;span class="o"&gt;((&lt;/span&gt;post&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
        &amp;lt;BlogPostPreview &lt;span class="nv"&gt;post&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;post&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;post.attributes.slug&lt;span class="o"&gt;}&lt;/span&gt; /&amp;gt;
      &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
        &amp;lt;h4 &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"font-mono text-black text-lg sm:col-span-2 lg:col-span-3 text-center"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          No results
        &amp;lt;/h4&amp;gt;
      &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When there are search results you want to show a &lt;code&gt;BlogPostPreview&lt;/code&gt; for every one of those. But if the search results array is empty you want to let the user know, that there was no results.&lt;/p&gt;

&lt;p&gt;And finally a component itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;section &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-8 mx-4"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;h2 &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"font-mono text-black text-xl md:text-4xl text-center mb-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        Search results &lt;span class="k"&gt;for&lt;/span&gt;: &amp;amp;quot&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;query&lt;span class="o"&gt;}&lt;/span&gt;&amp;amp;quot&lt;span class="p"&gt;;&lt;/span&gt;
      &amp;lt;/h2&amp;gt;
      &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 "&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;searchResults ? &lt;span class="o"&gt;(&lt;/span&gt;
          preparePostPreviews&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;)&lt;/span&gt; : &lt;span class="o"&gt;(&lt;/span&gt;
          &amp;lt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &amp;lt;BlogPostPreviewSkeleton /&amp;gt;
            &amp;lt;BlogPostPreviewSkeleton /&amp;gt;
            &amp;lt;BlogPostPreviewSkeleton /&amp;gt;
          &amp;lt;/&amp;gt;
        &lt;span class="o"&gt;)}&lt;/span&gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Until the &lt;em&gt;GraphQL&lt;/em&gt; query is finished you render the skeleton content, but as soon as the data is through - you use &lt;code&gt;preparePostPreviews&lt;/code&gt; function to display the content properly.&lt;/p&gt;

&lt;p&gt;Before you hook everything up you need to do a small adjustment in &lt;code&gt;BlogPostPreview&lt;/code&gt;. You’ll display &lt;code&gt;publishDate&lt;/code&gt; of every post. In your code add this snippet just over the &lt;code&gt;div&lt;/code&gt; containing the title:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;h6 &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"font-mono text-black text-xs mb-2"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;new Date&lt;span class="o"&gt;(&lt;/span&gt;post.attributes.publishedAt&lt;span class="o"&gt;)&lt;/span&gt;.toLocaleString&lt;span class="o"&gt;()}&lt;/span&gt;
&amp;lt;/h6&amp;gt;
&amp;lt;hr &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mb-2"&lt;/span&gt; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you need to make sure that &lt;code&gt;publishedAt&lt;/code&gt; is always fetched. Go through your &lt;em&gt;GraphQL&lt;/em&gt; queries and add &lt;code&gt;publishedAt&lt;/code&gt; as an additional fragment of data to be fetched. For example in &lt;code&gt;[slug].js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;data &lt;span class="o"&gt;{&lt;/span&gt;
  attributes &lt;span class="o"&gt;{&lt;/span&gt;
    title
    slug
    content
    publishedAt
    cover &lt;span class="o"&gt;{&lt;/span&gt;
      data &lt;span class="o"&gt;{&lt;/span&gt;
        attributes &lt;span class="o"&gt;{&lt;/span&gt;
          url
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this is done you can test out your newly created Search Results page. Go to &lt;a href="http://localhost:3000/search/YOUR-QUERY" rel="noopener noreferrer"&gt;localhost:3000/search/YOUR-QUERY&lt;/a&gt; (replace YOUR-QUERY with some text that occurs in one of your posts). After a brief moment you should see the results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffl1wtdwtti3rsqv94418.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffl1wtdwtti3rsqv94418.jpg" alt="Blog-6-2.jpg" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’re not finished though! One more thing to do! Let’s create a search bar on the navigation bar.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;components/Navbar.jsx&lt;/code&gt; add another item into the &lt;code&gt;flex&lt;/code&gt; container (it should be the last one):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;form &lt;span class="nv"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;handleSearch&lt;span class="o"&gt;}&amp;gt;&lt;/span&gt;
  &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &amp;lt;label
      &lt;span class="nv"&gt;htmlFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"location-search"&lt;/span&gt;
      &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mb-2 text-sm font-medium text-gray-900 sr-only dark:text-gray-300"&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      Your Email
    &amp;lt;/label&amp;gt;
    &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex w-full"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;input
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"search"&lt;/span&gt;
        &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'location-search"'&lt;/span&gt;
        &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rounded-l-lg rounded-r-none block p-2.5 z-20 text-sm text-gray-900 bg-gray-50 border-l-gray-50 border-l-2 border-r-0 border border-gray-300 focus:ring-blue-500 focus:border-blue-50"&lt;/span&gt;
        &lt;span class="nv"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Search..."&lt;/span&gt;
        &lt;span class="nv"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
      /&amp;gt;
      &amp;lt;button
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"submit"&lt;/span&gt;
        &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"p-2.5 text-sm font-medium text-gray-400 bg-gray-50 rounded-r-lg border border-gray-300 focus:ring-4 focus:border-blue-500 focus:ring-blue-500"&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &amp;lt;svg
          aria-hidden&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;
          &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-5 h-5"&lt;/span&gt;
          &lt;span class="nv"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;
          &lt;span class="nv"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"currentColor"&lt;/span&gt;
          &lt;span class="nv"&gt;viewBox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 0 24 24"&lt;/span&gt;
          &lt;span class="nv"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &amp;lt;path
            &lt;span class="nv"&gt;strokeLinecap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"round"&lt;/span&gt;
            &lt;span class="nv"&gt;strokeLinejoin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"round"&lt;/span&gt;
            &lt;span class="nv"&gt;strokeWidth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;
            &lt;span class="nv"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"&lt;/span&gt;
          &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;lt;/path&amp;gt;
        &amp;lt;/svg&amp;gt;
        &amp;lt;span &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sr-only"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;Search&amp;lt;/span&amp;gt;
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;handleSearch&lt;/code&gt; method will be implemented in a minute. The navigation bar should look like this now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgsmhzpss0tpj7mdeh4t9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgsmhzpss0tpj7mdeh4t9.jpg" alt="Blog-6-3.jpg" width="800" height="58"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now whenever user uses the search button or press &lt;code&gt;Enter&lt;/code&gt; in this input they should be redirected to &lt;code&gt;/search/SEARCH-QUERY&lt;/code&gt;. Let’s implement this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;const Navbar &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  const router &lt;span class="o"&gt;=&lt;/span&gt; useRouter&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  const handleSearch &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;e&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    e.preventDefault&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    router.push&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/search/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.target[0].value&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
// rest of the code...
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First you need to use &lt;code&gt;preventDefault&lt;/code&gt; because when form is submitted the site is reloaded and you don’t want it here. Second you need to use &lt;code&gt;next/router&lt;/code&gt; to redirect user. It’s that simple. Rebuild your app, start your dev server and try searching for something with this new search bar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02qw48fqdcw4tixq1i69.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02qw48fqdcw4tixq1i69.jpg" alt="Blog-6-4.jpg" width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s it - now publish your changes on &lt;em&gt;Netlify&lt;/em&gt; (you know how to do this already!). In the next part of this guide you’ll start to provide SEO for your blog posts.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Displaying all posts for specific Tag [Building Personal Blog Website Part 5]</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Wed, 21 Sep 2022 11:43:30 +0000</pubDate>
      <link>https://dev.to/hwlkdev/displaying-all-posts-for-specific-tag-building-personal-blog-website-part-5-1mno</link>
      <guid>https://dev.to/hwlkdev/displaying-all-posts-for-specific-tag-building-personal-blog-website-part-5-1mno</guid>
      <description>&lt;p&gt;The blog post preview cards on your homepage display not only the cover, title and excerpt, but also a list of tags. It would be great if user could click on any of the tags and then see all blog posts connected to it. And that’s what you’ll implement today.&lt;/p&gt;

&lt;p&gt;Let’s start with creating a new route in the &lt;em&gt;Next.js&lt;/em&gt; application. Create a new folder &lt;code&gt;tag&lt;/code&gt; in your &lt;code&gt;pages&lt;/code&gt; directory. Inside create a file &lt;code&gt;[tag].js&lt;/code&gt; - the goal is to have a route like &lt;code&gt;YOUR_URL/tag/tag-name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jODemY8E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1662618153/Blog_5_1_7edcae4f38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jODemY8E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1662618153/Blog_5_1_7edcae4f38.png" alt="Blog-5-1.png" width="592" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now inside this &lt;code&gt;[tag].js&lt;/code&gt; file you have to implement &lt;code&gt;getStaticPaths&lt;/code&gt; and &lt;code&gt;getStaticProps&lt;/code&gt; similarly to the Blog Post page.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;getStaticPaths&lt;/code&gt; you need to get a list of all tags that your CMS has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;async &lt;span class="k"&gt;function &lt;/span&gt;getStaticPaths&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  const &lt;span class="o"&gt;{&lt;/span&gt; data &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; await client.query&lt;span class="o"&gt;({&lt;/span&gt;
    query: gql&lt;span class="sb"&gt;`&lt;/span&gt;
      query Tags &lt;span class="o"&gt;{&lt;/span&gt;
        tags &lt;span class="o"&gt;{&lt;/span&gt;
          data &lt;span class="o"&gt;{&lt;/span&gt;
            attributes &lt;span class="o"&gt;{&lt;/span&gt;
              tagId
            &lt;span class="o"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="sb"&gt;`&lt;/span&gt;,
  &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    paths: data.tags.data.map&lt;span class="o"&gt;((&lt;/span&gt;item&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;({&lt;/span&gt;
      params: &lt;span class="o"&gt;{&lt;/span&gt; tag: item.attributes.tagId &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;}))&lt;/span&gt;,
    fallback: &lt;span class="nb"&gt;false&lt;/span&gt;,
  &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;getStaticProps&lt;/code&gt; you’ll get all of the posts with specific tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;async &lt;span class="k"&gt;function &lt;/span&gt;getStaticProps&lt;span class="o"&gt;({&lt;/span&gt; params &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  const &lt;span class="o"&gt;{&lt;/span&gt; data &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; await client.query&lt;span class="o"&gt;({&lt;/span&gt;
    query: gql&lt;span class="sb"&gt;`&lt;/span&gt;
      query Tags &lt;span class="o"&gt;{&lt;/span&gt;
        tags &lt;span class="o"&gt;(&lt;/span&gt;
          filters: &lt;span class="o"&gt;{&lt;/span&gt; tagId: &lt;span class="o"&gt;{&lt;/span&gt; eq: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.tag&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          data &lt;span class="o"&gt;{&lt;/span&gt;
            attributes &lt;span class="o"&gt;{&lt;/span&gt;
              name
              tagId
              posts &lt;span class="o"&gt;{&lt;/span&gt;
                data &lt;span class="o"&gt;{&lt;/span&gt;
                  attributes &lt;span class="o"&gt;{&lt;/span&gt;
                    title
                    slug
                    excerpt
                    publishedAt
                    cover &lt;span class="o"&gt;{&lt;/span&gt;
                        data &lt;span class="o"&gt;{&lt;/span&gt;
                            attributes &lt;span class="o"&gt;{&lt;/span&gt;
                                url
                            &lt;span class="o"&gt;}&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt;
                    tags &lt;span class="o"&gt;{&lt;/span&gt;
                        data &lt;span class="o"&gt;{&lt;/span&gt;
                            attributes &lt;span class="o"&gt;{&lt;/span&gt;
                                tagId
                                name
                            &lt;span class="o"&gt;}&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;}&lt;/span&gt;
                  &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
              &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="sb"&gt;`&lt;/span&gt;,
  &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    props: &lt;span class="o"&gt;{&lt;/span&gt;
      tagData: data.tags.data[0],
    &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that out of the way you will display the results in a similar manner as on the homepage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;default &lt;span class="k"&gt;function &lt;/span&gt;Tag&lt;span class="o"&gt;({&lt;/span&gt; tagData &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;section &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-8 mx-4"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;h2 &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"font-mono text-black text-xl md:text-4xl text-center mb-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        Articles with &amp;amp;quot&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;tagData.attributes.name&lt;span class="o"&gt;}&lt;/span&gt;&amp;amp;quot&lt;span class="p"&gt;;&lt;/span&gt; tag
      &amp;lt;/h2&amp;gt;
      &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 "&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;tagData.attributes.posts.data.map&lt;span class="o"&gt;((&lt;/span&gt;post&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
          &amp;lt;BlogPostPreview &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;post.attributes.slug&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;post&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;post&lt;span class="o"&gt;}&lt;/span&gt; /&amp;gt;
        &lt;span class="o"&gt;))}&lt;/span&gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of your work is already done, but you have one more thing to do - hook up this newly created page with &lt;code&gt;Tags&lt;/code&gt; on the &lt;code&gt;BlogPostPreview&lt;/code&gt;. Adjust &lt;code&gt;components/BlogPostPreview.jsx&lt;/code&gt; - instead of a blank &lt;code&gt;&amp;lt;a href=”#”&amp;gt;&lt;/code&gt; use the following &lt;code&gt;next/link&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"px-6 pt-4 pb-2"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;post.attributes.tags.data.map&lt;span class="o"&gt;((&lt;/span&gt;tag&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
          &amp;lt;Link
            &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/tag/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.attributes.tagId&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;tag.attributes.tagId&lt;span class="o"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &amp;lt;a&amp;gt;
              &amp;lt;span &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;{&lt;/span&gt;tag.attributes.name&lt;span class="o"&gt;}&lt;/span&gt;
              &amp;lt;/span&amp;gt;
            &amp;lt;/a&amp;gt;
          &amp;lt;/Link&amp;gt;
        &lt;span class="o"&gt;))}&lt;/span&gt;
      &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now rebuild your app and open it in dev mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn build
yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your blog and click on any Tag. It should open a list of blog posts with this tag:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a7ecq7yQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1662618155/Blog_5_2_9caf7c95f8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a7ecq7yQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1662618155/Blog_5_2_9caf7c95f8.png" alt="Blog-5-2.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last thing to do is to make this change live on &lt;em&gt;Netlify&lt;/em&gt;. Commit all of your changes and push it to &lt;em&gt;GitHub&lt;/em&gt;. &lt;em&gt;Netlify&lt;/em&gt; will rebuild your app and in a few minutes you’ll be able to use your new feature online. Well done!&lt;/p&gt;

</description>
      <category>strapi</category>
      <category>nextjs</category>
      <category>javascript</category>
      <category>headless</category>
    </item>
    <item>
      <title>Deploying Next.js app on Netlify [Building Personal Blog Website Part 4]</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Mon, 05 Sep 2022 11:52:11 +0000</pubDate>
      <link>https://dev.to/hwlkdev/deploying-nextjs-app-on-netlify-building-personal-blog-website-part-4-8m6</link>
      <guid>https://dev.to/hwlkdev/deploying-nextjs-app-on-netlify-building-personal-blog-website-part-4-8m6</guid>
      <description>&lt;p&gt;Now it’s finally time to put your app online! &lt;/p&gt;

&lt;p&gt;First you need to push your &lt;em&gt;Git&lt;/em&gt; repository (create automatically while creating &lt;em&gt;Next.js&lt;/em&gt; app) to GitHub. Go to &lt;a href="http://github.com"&gt;github.com&lt;/a&gt; and log in (or create an account if you didn’t do this already). Create a new repository clicking on the upper right corner:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_iUrIs-R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835599/Blog_4_1_b23476627b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_iUrIs-R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835599/Blog_4_1_b23476627b.png" alt="Blog-4-1.png" width="576" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the name, decide whether the repository should be private or public and click &lt;em&gt;Create&lt;/em&gt;. You should see something like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2ukieqKL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835600/Blog_4_2_57901869fd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2ukieqKL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835600/Blog_4_2_57901869fd.png" alt="Blog-4-2.png" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now go to the &lt;em&gt;Terminal&lt;/em&gt; and in your app’s root folder commit all the files (if you put your environment variables into &lt;code&gt;.env&lt;/code&gt; file then remember to add it to &lt;code&gt;.gitignore&lt;/code&gt;). Now let’s push your repository to &lt;em&gt;GitHub&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add origin YOUR_GITHUB_REPOSITORY
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it’s through you should be able to see your files in &lt;em&gt;GitHub&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z7RFNKcd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835600/Blog_4_3_404fa73d74.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z7RFNKcd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835600/Blog_4_3_404fa73d74.png" alt="Blog-4-3.png" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you’re ready to deploy it to &lt;em&gt;Netlify&lt;/em&gt;. Go to &lt;a href="http://netlify.com"&gt;netlify.com&lt;/a&gt; and log in. I suggest logging in with &lt;em&gt;GitHub&lt;/em&gt;. You should land on your dashboard where you can create a new site:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sO-WZtmC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835600/Blog_4_4_5cbfd56817.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sO-WZtmC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835600/Blog_4_4_5cbfd56817.png" alt="Blog-4-4.png" width="800" height="627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;em&gt;“Import an existing project”&lt;/em&gt; and then connect to &lt;em&gt;GitHub&lt;/em&gt; and select your repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wYwlIGL4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835684/Blog_4_5_a394ac5079.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wYwlIGL4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835684/Blog_4_5_a394ac5079.png" alt="Blog-4-5.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gzdUjptQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835684/Blog_4_6_a42e0c8aac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gzdUjptQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835684/Blog_4_6_a42e0c8aac.png" alt="Blog-4-6.png" width="800" height="1237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Leave all of the default values and click &lt;em&gt;Deploy site&lt;/em&gt;. You’ll see that deployment is in progress. Wait a few minutes until it is done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---u9pjE4N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835683/Blog_4_7_ae45472279.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---u9pjE4N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835683/Blog_4_7_ae45472279.png" alt="Blog-4-7.png" width="800" height="709"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;In case the deploy fails just click on the failed deploy and try doing it again. &lt;em&gt;Railway&lt;/em&gt; sometimes has an issue of rejecting connections at the beginning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pJBLI_gm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835684/Blog_4_8_f8475c5a9e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pJBLI_gm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835684/Blog_4_8_f8475c5a9e.png" alt="Blog-4-8.png" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;When it’s done you’ll see the address of you new website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MeXexGmM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835834/Blog_4_9_e7684c55d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MeXexGmM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835834/Blog_4_9_e7684c55d6.png" alt="Blog-4-9.png" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to this address and you should see your work online!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--73O2ipeJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835835/Blog_4_10_1be0c22173.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--73O2ipeJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835835/Blog_4_10_1be0c22173.png" alt="Blog-4-10.png" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now whenever you push something to your repository &lt;em&gt;Netlify&lt;/em&gt; will automatically rebuild your app. Remember! Changing something in your CMS does not trigger rebuilding so if you want to have fresh content - you’ll have to rebuild your app on &lt;em&gt;Netlify&lt;/em&gt; manually on the &lt;em&gt;Deploys&lt;/em&gt; tab on your &lt;em&gt;Netlify&lt;/em&gt; dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hAOW_Pib--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835834/Blog_4_11_6b56a54759.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hAOW_Pib--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835834/Blog_4_11_6b56a54759.png" alt="Blog-4-11.png" width="768" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s make some changes to the site’s layout to see this automatic rebuild in action.&lt;/p&gt;

&lt;p&gt;What your site lacks as of this moment is a nice navigation bar, basic footer and some responsive adjustments for the mobile. Create two new components in &lt;code&gt;components&lt;/code&gt; directory:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Navbar.jsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;import Link from &lt;span class="s2"&gt;"next/link"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import React from &lt;span class="s2"&gt;"react"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

const Navbar &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&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;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;nav &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"z-0 w-full"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"z-10 bg-blue-500 shadow"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"max-w-7xl mx-auto px-2 sm:px-4 lg:px-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex flex-col sm:flex-row items-center justify-between gap-4 p-2 font-mono"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &amp;lt;Link &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &amp;lt;a &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex-shrink-0"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &amp;lt;h1 &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"font-bold text-xl uppercase text-white"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  My Personal Blog
                &amp;lt;/h1&amp;gt;
              &amp;lt;/a&amp;gt;
            &amp;lt;/Link&amp;gt;
            &amp;lt;div&amp;gt;
              &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex gap-2"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &amp;lt;Link &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  &amp;lt;a &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"px-3 py-2 text-sm font-medium text-white hover:underline hover:underline-offset-4 hover:text-white hover:font-bold transition duration-150 ease-in-out cursor-pointer focus:outline-none focus:text-white focus:bg-gray-700 "&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    Home
                  &amp;lt;/a&amp;gt;
                &amp;lt;/Link&amp;gt;
                &amp;lt;Link &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/about"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  &amp;lt;a &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"px-3 py-2 text-sm font-medium text-white hover:underline hover:underline-offset-4 hover:text-white hover:font-bold transition duration-150 ease-in-out cursor-pointer focus:outline-none focus:text-white focus:bg-gray-700 "&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    About
                  &amp;lt;/a&amp;gt;
                &amp;lt;/Link&amp;gt;
                &amp;lt;Link &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/contect"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  &amp;lt;a &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"px-3 py-2 text-sm font-medium text-white hover:underline hover:underline-offset-4 hover:text-white hover:font-bold transition duration-150 ease-in-out cursor-pointer focus:outline-none focus:text-white focus:bg-gray-700 "&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    Contact
                  &amp;lt;/a&amp;gt;
                &amp;lt;/Link&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/nav&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;default Navbar&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and &lt;code&gt;Footer.jsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;import React from &lt;span class="s2"&gt;"react"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

const Footer &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&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;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;footer &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bg-blue-500 py-8 w-full"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex flex-wrap justify-center"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-sm text-white font-mono font-semibold py-1"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          Copyright © YOUR_NAME 2022
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/footer&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;default Footer&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now adjust your &lt;code&gt;pages/_app.js&lt;/code&gt; file to use those newly created components&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;MyApp&lt;span class="o"&gt;({&lt;/span&gt; Component, pageProps &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex flex-col items-center bg-white"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;Navbar /&amp;gt;
      &amp;lt;Component &lt;span class="o"&gt;{&lt;/span&gt;...pageProps&lt;span class="o"&gt;}&lt;/span&gt; /&amp;gt;
      &amp;lt;Footer /&amp;gt;
    &amp;lt;/div&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally make small changes in &lt;code&gt;pages/index.js&lt;/code&gt; to better display the post list on various screen resolutions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;default &lt;span class="k"&gt;function &lt;/span&gt;Home&lt;span class="o"&gt;({&lt;/span&gt; posts &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;section &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 my-8 mx-4"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;{&lt;/span&gt;posts.map&lt;span class="o"&gt;((&lt;/span&gt;post&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
        &amp;lt;BlogPostPreview &lt;span class="nv"&gt;post&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;post&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;post.attributes.slug&lt;span class="o"&gt;}&lt;/span&gt; /&amp;gt;
      &lt;span class="o"&gt;))}&lt;/span&gt;
    &amp;lt;/section&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now commit all of the files and push the changes to GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait a few minutes and check your website hosted on Netlify.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2yHMzyzM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835835/Blog_4_12_59e4f05d00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2yHMzyzM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661835835/Blog_4_12_59e4f05d00.png" alt="Blog-4-12.png" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looks better, doesn’t it? &lt;/p&gt;

&lt;p&gt;And that’s it for today - your website starts going somewhere! In the next part of the series you’ll implement a simple &lt;em&gt;Show all posts for specific Tag&lt;/em&gt; feature. See you then!&lt;/p&gt;

</description>
      <category>strapi</category>
      <category>nextjs</category>
      <category>javascript</category>
      <category>headlesscms</category>
    </item>
    <item>
      <title>Basic Next.js app - blog post page [Building Personal Blog Website Part 3]</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Mon, 29 Aug 2022 11:35:00 +0000</pubDate>
      <link>https://dev.to/hwlkdev/basic-nextjs-app-blog-post-page-building-personal-blog-website-part-3-llf</link>
      <guid>https://dev.to/hwlkdev/basic-nextjs-app-blog-post-page-building-personal-blog-website-part-3-llf</guid>
      <description>&lt;p&gt;You can see all your posts already on your homepage as cards. But now let’s go a step further - create a subpage for every post.&lt;/p&gt;

&lt;p&gt;Before you create a subpage for a specific blog post - let’s just do a small adjustment in your current app. You’ll make changes in the &lt;code&gt;_app.js&lt;/code&gt; file. Markup in this file is preserved between pages and works as a kind of a page layout. By default it renders just a page component. Now you’ll move the header from your homepage there. Use this code in &lt;code&gt;_app.js&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;function MyApp({ Component, pageProps }) {
  return (
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col items-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"text-3xl uppercase font-serif font-bold my-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        My personal blog
      &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Component&lt;/span&gt; &lt;span class="err"&gt;{...&lt;/span&gt;&lt;span class="na"&gt;pageProps&lt;/span&gt;&lt;span class="err"&gt;}&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;p&gt;And remove the same markup from your &lt;code&gt;index.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;export default function Home({ posts }) {
  return (
    &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"grid grid-cols-3 gap-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      {posts.map((post) =&amp;gt; (
        &lt;span class="nt"&gt;&amp;lt;BlogPostPreview&lt;/span&gt; &lt;span class="na"&gt;post=&lt;/span&gt;&lt;span class="s"&gt;{post}&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;{post.attributes.slug}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      ))}
    &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now whenever you’ll create a new subpage - it’ll always have the header &lt;em&gt;“My personal blog”&lt;/em&gt;. Of course it would be best to add a navigation bar and footer here, but that’s not the main focus in this article series, so we’ll go back to this in the future, when there will be something to navigate to.&lt;/p&gt;

&lt;p&gt;The next step would be creating a page for every blog post. Of course you’d want it to be generic so with every new post you’d get a new &lt;code&gt;/post/slug-of-blogpost&lt;/code&gt; page. You can easily achieve that in &lt;em&gt;Next.js&lt;/em&gt; using &lt;strong&gt;dynamic routes&lt;/strong&gt;. Dynamic routes allow you to create a template page and then during site generation all the needed information are figured out by &lt;em&gt;Next.js&lt;/em&gt;. Let’s just see how this works.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;pages&lt;/code&gt; directory create a new folder &lt;code&gt;post&lt;/code&gt;. This will correspond to a new route &lt;code&gt;/post&lt;/code&gt;. Inside create a file &lt;code&gt;[slug].js&lt;/code&gt; - &lt;strong&gt;square brackets are important&lt;/strong&gt; - they indicate that this is a dynamic route. In our case it will allow us to get a specific route for each blog post (for example &lt;code&gt;/post/test-post&lt;/code&gt; or &lt;code&gt;/post/my-first-post&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661580192%2FBlog_tutorial_3_1_ffb6c9af5b.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661580192%2FBlog_tutorial_3_1_ffb6c9af5b.png" alt="Blog-tutorial-3-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you are creating a statically generated site you need to tell &lt;em&gt;Next.js&lt;/em&gt; which routes it should generate. One way to provide this information is by creating a &lt;code&gt;getStaticPaths&lt;/code&gt; function in your &lt;code&gt;[slug].js&lt;/code&gt; file. This function will get slugs of all the blog posts and based on that it will inform &lt;em&gt;Next.js&lt;/em&gt; which routes are needed.&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticPaths&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;data&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
      query Posts {
        posts(
          filters: { publishedAt: { notNull: true } }
        ) {
          data {
            attributes {
              slug
            }
          }
        }
      }
    `&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;paths&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;posts&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&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="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="na"&gt;fallback&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;p&gt;The &lt;code&gt;filters&lt;/code&gt; in the query ensure that you will get only published posts.&lt;/p&gt;

&lt;p&gt;Now you have the list of slugs and it’s time to use it to fetch every post data. Let’s create a &lt;code&gt;getStaticProps&lt;/code&gt; method in the same file.&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&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;data&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
      query Posts {
        posts(
          sort: "publishedAt:desc"
          pagination: { limit: 1 }
          filters: { slug: { eq: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" } }
        ) {
          data {
            attributes {
              title
              slug
              content
              cover {
                data {
                  attributes {
                    url
                  }
                }
              }
              author {
                data {
                  attributes {
                    username
                  }
                }
              }
              tags {
                data {
                  attributes {
                    tagId
                    name
                  }
                }
              }
            }
          }
        }
      }
    `&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="na"&gt;postData&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;posts&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="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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll get all the data needed to properly display the post on your page. You query the &lt;em&gt;GraphQL&lt;/em&gt; for a &lt;code&gt;post&lt;/code&gt; with a specific &lt;code&gt;slug&lt;/code&gt;. Unfortunately &lt;em&gt;Strapi API&lt;/em&gt; does not allow you to get one specific post using the slug. It’s only possible using &lt;code&gt;post ID&lt;/code&gt;, but you want to have more descriptive links than for example &lt;code&gt;/post/2&lt;/code&gt;. &lt;code&gt;/post/a-blog-post&lt;/code&gt; looks definitely better. That’s why you’ll make a standard &lt;code&gt;Posts&lt;/code&gt; query with a filter to get the one with a specific slug. And the returned data is put into &lt;code&gt;props&lt;/code&gt; of &lt;code&gt;BlogPost&lt;/code&gt; component which you’ll create next.&lt;/p&gt;

&lt;p&gt;Now the time has come to create your blog post page. Still in [slug].js add a new component (place it over the getStaticPaths and getStaticProps methods).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;default &lt;span class="k"&gt;function &lt;/span&gt;Post&lt;span class="o"&gt;({&lt;/span&gt; postData &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &amp;lt;div&amp;gt;&lt;span class="o"&gt;{&lt;/span&gt;postData.attributes.content&lt;span class="o"&gt;}&lt;/span&gt;&amp;lt;/div&amp;gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But wait! Your content is written in &lt;em&gt;Markdown&lt;/em&gt; - so the end result would look 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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661580194%2FBlog_tutorial_3_2_f82162b2cd.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661580194%2FBlog_tutorial_3_2_f82162b2cd.png" alt="Blog-tutorial-3-2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s not exactly what we want! To help you display &lt;em&gt;Markdown&lt;/em&gt; properly I suggest using two additional libraries - &lt;code&gt;react-markdown&lt;/code&gt; and &lt;code&gt;TailwindCSS Typography&lt;/code&gt;. First one help format markdown properly, second one is a plugin for &lt;em&gt;TailwindCSS&lt;/em&gt; which provides basic styling for articles. Let’s install both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add react-markdown @tailwindcss/typography
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now update your &lt;code&gt;tailwind.config.js&lt;/code&gt; so it’s aware of the newly installed plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/&lt;span class="k"&gt;**&lt;/span&gt; @type &lt;span class="o"&gt;{&lt;/span&gt;import&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tailwindcss'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.Config&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;/
module.exports &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  content: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"./pages/**/*.{js,ts,jsx,tsx}"&lt;/span&gt;,
    &lt;span class="s2"&gt;"./components/**/*.{js,ts,jsx,tsx}"&lt;/span&gt;,
  &lt;span class="o"&gt;]&lt;/span&gt;,
  theme: &lt;span class="o"&gt;{&lt;/span&gt;
    extend: &lt;span class="o"&gt;{}&lt;/span&gt;,
  &lt;span class="o"&gt;}&lt;/span&gt;,
  plugins: &lt;span class="o"&gt;[&lt;/span&gt;require&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"@tailwindcss/typography"&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;,
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this out of the way you’ll be able display your blog posts properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;default &lt;span class="k"&gt;function &lt;/span&gt;Post&lt;span class="o"&gt;({&lt;/span&gt; postData &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &amp;lt;ReactMarkdown &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"prose prose-sm md:prose-lg lg:prose-xl xl:prose-2xl prose-slate w-11/12 mx-4 my-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;{&lt;/span&gt;postData.attributes.content&lt;span class="o"&gt;}&lt;/span&gt;
    &amp;lt;/ReactMarkdown&amp;gt;
  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have one more thing to do before you’ll be able to see your blog posts in action. You need to hook Blog Post Page to the Blog Post Preview - so when the user clicks on the preview he/she will go to the full post. &lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;next/link&lt;/code&gt; which is better suited for routing in &lt;em&gt;Next.js&lt;/em&gt; apps. Go to the &lt;code&gt;BlogPostPreview.jsx&lt;/code&gt; and replace existing &lt;code&gt;&amp;lt;a href=”#”&amp;gt;&lt;/code&gt; and its content with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;Link &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/post/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.attributes.slug&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;}&amp;gt;&lt;/span&gt;
  &amp;lt;a&amp;gt;
    &amp;lt;Image
      &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-full"&lt;/span&gt;
      &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;post.attributes.cover.data.attributes.url&lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="nv"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
      &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;1000&lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;480&lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="nv"&gt;objectFit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"cover"&lt;/span&gt;
    /&amp;gt;
    &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"px-6 py-4"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"font-bold text-xl mb-2"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;post.attributes.title&lt;span class="o"&gt;}&lt;/span&gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;p &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-gray-700 text-base"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;{&lt;/span&gt;post.attributes.excerpt&lt;span class="o"&gt;}&lt;/span&gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now rebuild your app and then start the development mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn build
yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:3000&lt;/code&gt; and click on any of the previews - you should be redirected to the blog post page:&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661580194%2FBlog_tutorial_3_3_ffff866bf1.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661580194%2FBlog_tutorial_3_3_ffff866bf1.png" alt="Blog-tutorial-3-3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s better! Of course feel free to experiment with the styling of the whole page - I’m not focusing on this here as this is not a course on css and layouts.&lt;/p&gt;

&lt;p&gt;And that’s it! In the next part of this guide you’ll finally deploy the page on &lt;em&gt;Netlify&lt;/em&gt;, so stay tuned!&lt;/p&gt;

</description>
      <category>strapi</category>
      <category>nextjs</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Hosting free Strapi CMS on Railway [Building Personal Blog Website Part 1]</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Fri, 26 Aug 2022 12:24:00 +0000</pubDate>
      <link>https://dev.to/hwlkdev/hosting-free-strapi-cms-on-railway-building-personal-blog-website-part-1-1epe</link>
      <guid>https://dev.to/hwlkdev/hosting-free-strapi-cms-on-railway-building-personal-blog-website-part-1-1epe</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the first part of the "Building Personal Blog Website" series. In the series we'll setup a free CMS to hold our blog content on Railway, create a React app with Next.js' static site generation and TailwindCSS to present the articles and host it on Netlify.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;❗ This is an updated version of the guide. The original one used Heroku instead of Railway, but as Heroku phased out its free tier - I decided to rewrite the first parts of this series to still use only free resources.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All articles:&lt;br&gt;
&lt;a href="https://dev.to/hawelkam/hosting-free-strapi-cms-on-railway-building-personal-blog-website-part-1-1epe"&gt;Part 1: Hosting free Strapi CMS on Railway&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Whether you want to create a simple blog or a portfolio website it is always a good idea to use a good CMS (Content Management System) behind. For quite some time Wordpress was a go-to solution, but now the choice is not so obvious. As an ambitious developer you can of course try to write your own CMS, but let's be frank - in most cases the best way is to use an existing one. That was my approach when I started to work on my personal website.&lt;/p&gt;

&lt;p&gt;My main interest were as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ability to change website's content without changing the code&lt;/li&gt;
&lt;li&gt;simple blog functionality&lt;/li&gt;
&lt;li&gt;out-of-the box GraphQL integration&lt;/li&gt;
&lt;li&gt;cheap site maintenance (preferably free)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After some time of "research" I chose Strapi CMS for the first three points and Railway for the last one.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Strapi?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It's lightweight - compared to other CMSs Strapi is lightweight and really simple to set up.&lt;/li&gt;
&lt;li&gt;It's headless by design - I definitely prefer a headless CMS solution over a traditional CMS, it gives much more flexibility.&lt;/li&gt;
&lt;li&gt;It's written in JavaScript - this definitely helps with writing extensions/plugins if you use JS on a daily basis (which I am).&lt;/li&gt;
&lt;li&gt;It has really extensive docs - Strapi docs helped me in tons of situations, if you have a problem with Strapi it's almost guaranteed you'll find some help in the docs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why headless?
&lt;/h2&gt;

&lt;p&gt;Main reason - it's technology agnostic - traditional CMS requires you to write templates etc. in the technology the CMS is written. With headless this problem is gone. You utilize the REST/GraphQL endpoints to fetch the data and display it however you like.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to set this up?
&lt;/h2&gt;

&lt;p&gt;To set up your Strapi CMS you’ll use Railway. The process of deployment is rather easy, but to make it even easier you’ll start with setting up Cloudinary. If you do this now - configuring Strapi to use Cloudinary as an image repository will be a piece of cake - Railway will need the API keys and then it’ll set everything up automatically.&lt;/p&gt;

&lt;p&gt;As you are building a personal blog it is really possible that you'd want to use some images in the blog posts. The CMS is hosted on a free-tier Railway, so the storage is rather limited. That's why you need to think about other solutions. And here's where the cloud comes in.&lt;/p&gt;

&lt;p&gt;For this I have chosen &lt;em&gt;&lt;a href="https://cloudinary.com/" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt;&lt;/em&gt;. It's a rather popular platform for managing assets and optimizing images. But the main reason to use it for our personal project is that it has a free tier with easy-to-use API. Config is worry-free - you'll go through it today.&lt;/p&gt;

&lt;p&gt;But before you start - head out to &lt;a href="https://cloudinary.com/" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt;, create a free account (the process is rather standard) and when you're done - log in and get your SDK config (&lt;em&gt;Getting Started -&amp;gt; Configure your SDK -&amp;gt; Start configuring&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660973012%2F1_ddeb39219a.jpg" 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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660973012%2F1_ddeb39219a.jpg" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660973012/1_ddeb39219a.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save those variables somewhere and then let's create your Strapi app in Railway.&lt;/p&gt;

&lt;p&gt;Go to &lt;a href="https://railway.app/new/template/strapi" rel="noopener noreferrer"&gt;Railway Strapi builder&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Log in with your GitHub account and provide the necessary accesses.&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661514896%2FZrzut_ekranu_2022_08_26_o_05_15_20_fc5d21d90a.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661514896%2FZrzut_ekranu_2022_08_26_o_05_15_20_fc5d21d90a.png" alt="GitHub login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you do this you should see something like this (if you don’t - just refresh the page):&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661514962%2FStrapi_Config_244b403b15.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661514962%2FStrapi_Config_244b403b15.png" alt="Strapi Config"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you have to provide a name for your repository. Paste your Cloudinary keys in the specific fields and for the rest of those (&lt;code&gt;ADMIN_JWT_SECRET&lt;/code&gt;, &lt;code&gt;JWT_SECRET&lt;/code&gt;, &lt;code&gt;APP_KEYS&lt;/code&gt;, &lt;code&gt;API_TOKEN_SALT&lt;/code&gt;) - generate new values in your terminal with &lt;code&gt;openssl rand -base64 32&lt;/code&gt;(remember to save them somewhere).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;EDIT: All the variables you use here are also important for local development so save those in .env file (and remember to add .env file to your .gitignore if it's not already there).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you fill all the fields - click Deploy. And that’s it, it’s that simple. Wait a few minutes so your build will be done and then you’ll be able to continue with setting up your admin account for Strapi.&lt;/p&gt;



&lt;p&gt;If you want you can customize your Railway domain for the cms - when the build is done go the the last tab (Settings) and there you can easily edit the domain.&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661515345%2FZrzut_ekranu_2022_08_26_o_11_45_03_131f072ad9.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661515345%2FZrzut_ekranu_2022_08_26_o_11_45_03_131f072ad9.png" alt="Domain configuration"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Go to &lt;code&gt;https://[yourapp].up.railway.app/admin&lt;/code&gt; to set up the admin user and then log in (chances are you'll get the error about invalid API call - if so, don't worry, we'll fix it in a moment). You should land in the dashboard (if you had the error - ignore this part and just move along to the next paragraph).&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F1_c7bbc58357.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F1_c7bbc58357.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660883349/1_c7bbc58357.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the app is in production mode you can't really make any adjustments here. You can't create a new content-type or add a new plugin. That's why you need to go into developer mode - make changes there and redeploy the app.&lt;/p&gt;

&lt;p&gt;Strapi created by Railway depends on postgres database, but it’s definitely an overkill to use it locally. You’ll now configure a SQLite database to be used on your local environment. First, clone the repository created by Railway. Before you install and start the development server you’ll need to do a few adjustment.&lt;/p&gt;

&lt;p&gt;Copy the file &lt;code&gt;config/database.js&lt;/code&gt; to &lt;code&gt;config/env/production&lt;/code&gt;, then replace &lt;code&gt;config/database.js&lt;/code&gt; with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;const path &lt;span class="o"&gt;=&lt;/span&gt; require&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"path"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

module.exports &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;({&lt;/span&gt;
  connection: &lt;span class="o"&gt;{&lt;/span&gt;
    client: &lt;span class="s2"&gt;"sqlite"&lt;/span&gt;,
    connection: &lt;span class="o"&gt;{&lt;/span&gt;
      filename: path.join&lt;span class="o"&gt;(&lt;/span&gt;__dirname, &lt;span class="s2"&gt;".."&lt;/span&gt;, &lt;span class="s2"&gt;".tmp/data.db"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,
    &lt;span class="o"&gt;}&lt;/span&gt;,
    useNullAsDefault: &lt;span class="nb"&gt;true&lt;/span&gt;,
  &lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install &lt;em&gt;SQLite&lt;/em&gt; dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn &lt;span class="nb"&gt;install &lt;/span&gt;better-sqlite3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;: Before proceeding - upgrade Strapi version in your &lt;code&gt;package.json&lt;/code&gt; to at least 4.5.5 (and plugins also if you have any installed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    "@strapi/plugin-i18n": "4.5.5",
    "@strapi/plugin-users-permissions": "4.5.5",
    "@strapi/provider-upload-cloudinary": "4.5.5",
    "@strapi/strapi": "4.5.5",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now you’re ready to start the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn &lt;span class="nb"&gt;install
&lt;/span&gt;yarn develop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when the app starts you'll need to set up admin account once again (it's stored in &lt;em&gt;SQlite&lt;/em&gt; database, so it'll be gone after some time locally - but that's not a problem). Now when you log in you can create new content-types, configure plugins etc.&lt;/p&gt;

&lt;p&gt;Before starting creating content-types let's just quickly install a plugin that will be really useful later - GraphQL plugin which enables the default GraphQL endpoint for your CMS (you can skip it if you're ok with using standard REST calls, but I'll be using GraphQL in the React app). You can install it easily using yarn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add @strapi/plugin-graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that out of the way let's just create a blog post type.&lt;/p&gt;

&lt;p&gt;In the content-type builder select a "&lt;em&gt;Create a new collection type&lt;/em&gt;".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F2_b7d4fd1280.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F2_b7d4fd1280.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660883349/2_b7d4fd1280.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And add some mandatory fields to it. In my case it's &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;cover&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt; and &lt;code&gt;slug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F3_69d0f555f4.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F3_69d0f555f4.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660883349/3_69d0f555f4.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;author&lt;/code&gt; it's important to select a proper relation:&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F4_c86b422d41.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F4_c86b422d41.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660883349/4_c86b422d41.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For slug remember to make it of type &lt;code&gt;UID&lt;/code&gt; and select &lt;code&gt;title&lt;/code&gt; as an attached field:&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F5_82ab389d55.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F5_82ab389d55.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660883349/5_82ab389d55.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you save you can commit all the new files - the schema files etc. will tell the production app what kind of content should be possible to create.&lt;/p&gt;

&lt;p&gt;Now you are ready to redeploy the app, just do a push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you do this Railway will do an automatic redeploy. When it’s finished, log in with the admin credentials and do some final configuration.&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661515483%2FRedeploy_0823446f6a.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1661515483%2FRedeploy_0823446f6a.png" alt="Redeploy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you need to do is to give public access to posts. Do this by going into &lt;em&gt;Settings -&amp;gt; Users &amp;amp; Permissions Plugin -&amp;gt; Public -&amp;gt; Edit&lt;/em&gt; (icon).&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F6_05f4522a9e.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883349%2F6_05f4522a9e.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660883349/6_05f4522a9e.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While inside, give access to &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;findOne&lt;/code&gt; for &lt;code&gt;posts&lt;/code&gt;and &lt;code&gt;users-permissions&lt;/code&gt;. This way you'll be able to query the CMS for the list of blog posts and for a specific one post (using &lt;code&gt;slug&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883351%2F7_ee57f57682.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883351%2F7_ee57f57682.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660883351/7_ee57f57682.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;em&gt;Save&lt;/em&gt; and now let's create an author and a dummy blog post to test the endpoint (&lt;em&gt;Content Manager -&amp;gt; User -&amp;gt; Create New Entry&lt;/em&gt; and &lt;em&gt;Content Manager -&amp;gt; Post -&amp;gt; Create New Entry&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883351%2F8_2bba403389.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883351%2F8_2bba403389.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660883351/8_2bba403389.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on the area below &lt;code&gt;cover&lt;/code&gt; (or whatever you named this field) and just follow the instructions on the screen to add a new image.&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660973012%2F3_f5ea5d30fe.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660973012%2F3_f5ea5d30fe.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660973012/3_f5ea5d30fe.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660973012%2F4_aaea73ab1a.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660973012%2F4_aaea73ab1a.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660973012/4_aaea73ab1a.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After saving you should see in the blog post editor that the image is in fact there.&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660973012%2F5_c5ef86b456.jpg" 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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660973012%2F5_c5ef86b456.jpg" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660973012/5_c5ef86b456.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But now to be entirely sure the image was added correctly - go to the &lt;a href="https://cloudinary.com/console" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt; website, log in and go into &lt;em&gt;Media Library&lt;/em&gt;. There you should see your newly created image in 3-4 sizes.&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660973012%2F6_7c037242a8.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660973012%2F6_7c037242a8.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660973012/6_7c037242a8.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After saving a post you also need to publish it so it would be visible in the API.&lt;/p&gt;

&lt;p&gt;To check if everything works use &lt;em&gt;Postman&lt;/em&gt; (or a similar app).&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883351%2F9_136baa94d1.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883351%2F9_136baa94d1.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660883351/9_136baa94d1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open up Postman and in the address field put &lt;code&gt;https://[yourapp].up.railway.app/graphql/&lt;/code&gt; and select &lt;em&gt;POST&lt;/em&gt; type of query. Then in the body of the query select &lt;em&gt;graphql&lt;/em&gt; type and type this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query {
    posts {
        data {
            attributes {
                title
                content
            }
        }
    }
}

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

&lt;/div&gt;



&lt;p&gt;When you send this request, you'll receive the blog post that you just created a minute ago:&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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883351%2F10_593996f566.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%2Fdvrqjaeju%2Fimage%2Fupload%2Fv1660883351%2F10_593996f566.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1660883351/10_593996f566.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see the content is returned as markdown - it's much more efficient to send the data this way, but in our frontend app we'll need to convert it to HTML. We'll probably use something like &lt;a href="https://github.com/remarkjs/react-markdown" rel="noopener noreferrer"&gt;react-markdown&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And that's it! We now have a hosted Strapi CMS that is accessible by both REST and GraphQL. In the next part you’ll create the Next.js app to consume the data you already have in your CMS.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Setting up a basic Next.js app locally [Building Personal Blog Website Part 2]</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Sat, 20 Aug 2022 09:42:00 +0000</pubDate>
      <link>https://dev.to/hwlkdev/setting-up-a-basic-nextjs-app-locally-building-personal-blog-website-part-3-13e6</link>
      <guid>https://dev.to/hwlkdev/setting-up-a-basic-nextjs-app-locally-building-personal-blog-website-part-3-13e6</guid>
      <description>&lt;p&gt;Now that the basic CMS is set up on &lt;em&gt;Railway&lt;/em&gt; and &lt;em&gt;Cloudinary&lt;/em&gt; is configured to handle the images, the next step would be... creating a &lt;em&gt;Next&lt;/em&gt; app.&lt;/p&gt;

&lt;p&gt;This &lt;em&gt;Next&lt;/em&gt; app will consume content provided by the CMS and present it on a webpage. In this part of the series basic homepage with a list of blog posts will be created and set up locally.&lt;/p&gt;

&lt;p&gt;So let's just jump into it. First thing you need to create is a new &lt;em&gt;Next.js&lt;/em&gt; app. You can do this by this simple command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@latest

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

&lt;/div&gt;



&lt;p&gt;After running it the prompt will ask for the app's name - provide one and then the project will be created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bVKEQ716--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531654/Blog_tutorial_1_498857321b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bVKEQ716--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531654/Blog_tutorial_1_498857321b.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531654/Blog_tutorial_1_498857321b.png" width="800" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before you start working on an actual app let's just install some dependencies that will be useful later on - &lt;em&gt;TailwindCSS&lt;/em&gt; for styling the app and &lt;em&gt;Apollo Client&lt;/em&gt; for working with &lt;em&gt;GraphQL&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;First - let's go with &lt;em&gt;TailwindCSS&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add tailwindcss postcss autoprefixer
npx tailwindcss init -p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the init command the file &lt;code&gt;tailwind.config.js&lt;/code&gt; will be created. You need to make a few changes to the generated one so everything will be working correctly.&lt;/p&gt;

&lt;p&gt;Replace an empty &lt;code&gt;content&lt;/code&gt; array with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s2mPH9xs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531709/Blog_tutorial_2_8ca3d2c219.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s2mPH9xs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531709/Blog_tutorial_2_8ca3d2c219.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531709/Blog_tutorial_2_8ca3d2c219.png" width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You also need to import &lt;em&gt;TailwindCSS&lt;/em&gt; rules to the &lt;code&gt;./styles/globals.css&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@tailwind base;
@tailwind components;
@tailwind utilities;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R6TzYpQQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531983/Blog_tutorial_2_3_8b60adcecb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R6TzYpQQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531983/Blog_tutorial_2_3_8b60adcecb.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531983/Blog_tutorial_2_3_8b60adcecb.png" width="658" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And now you're done with setting up &lt;em&gt;TailwindCSS&lt;/em&gt;. Let's move on to the &lt;em&gt;Apollo Client&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add @apollo/client graphql

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

&lt;/div&gt;



&lt;p&gt;That's it! You're ready to create your &lt;em&gt;GraphQL-driven&lt;/em&gt; blog page.&lt;/p&gt;

&lt;p&gt;First you need to set up an &lt;em&gt;Apollo Client&lt;/em&gt;, that will work as a "gateway" to your &lt;em&gt;GraphQL&lt;/em&gt; API. Create a new file in the root of the project called apollo-client.js (you can name it however you want TBH).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ApolloClient, InMemoryCache } from "@apollo/client";

const client = new ApolloClient({
    uri: "https://[YOUR-CMS-GRAPHQL-URL]",
    cache: new InMemoryCache(),
});

export default client;

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

&lt;/div&gt;



&lt;p&gt;With this in place it's time to use it! You'll start by getting a list of all blog posts to be displayed on the homepage. You'll use the &lt;em&gt;Static Site Generation&lt;/em&gt; feature of &lt;em&gt;Next.js&lt;/em&gt; - for this the &lt;code&gt;getStaticProps&lt;/code&gt; method is needed.&lt;/p&gt;




&lt;h3&gt;
  
  
  Side note - Why SSG?
&lt;/h3&gt;

&lt;p&gt;As in this tutorial series we are talking about setting up this blog for free - we are limited performance-wise. The CMS set on free &lt;em&gt;Railway&lt;/em&gt; tier isn’t exactly a speed demon, &lt;em&gt;Next.js&lt;/em&gt; app with SSR set on free &lt;em&gt;Netlify&lt;/em&gt; would also be kinda slow. So, SSG seems to be the best option, even though every change on our blog would need an app rebuild. If budget is not a concern to you, then you can think about other options, but that’s not the topic of this series.&lt;/p&gt;




&lt;p&gt;Before we go any further let's just add two more fields in your Blog post configuration. Open up a repo for your &lt;em&gt;Strapi CMS&lt;/em&gt; and run it in development mode (&lt;code&gt;yarn develop&lt;/code&gt;). Log in and go to the Content-Type Builder. First you'll need to create a new type named &lt;em&gt;Tag&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It has an ID and a name that should be unique.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1yh8NMnn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_4_dd2a8f9c9c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1yh8NMnn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_4_dd2a8f9c9c.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_4_dd2a8f9c9c.png" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iG2-3NMV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_5_048bf8e3c6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iG2-3NMV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_5_048bf8e3c6.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_5_048bf8e3c6.png" width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When this is done you are ready to "upgrade" your Blog post type. In the Content-Type Builder open the Post and add two new fields. First one is excerpt - the short text that will be presented on your homepage, sneak peak into the blog post. It should be of type string and preferably have some maximum length (250 characters in my case). It also should be required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g37Q2tPK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_6_e2c852c029.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g37Q2tPK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_6_e2c852c029.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_6_e2c852c029.png" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DR1s8A70--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_7_5b3253ddae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DR1s8A70--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_7_5b3253ddae.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_7_5b3253ddae.png" width="800" height="572"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you need to add Tags. It will be a Relation type field and the relation is Many-to-Many as one Post can have multiple Tags and every Tag can belong to multiple Posts. Do it like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uMSKmoko--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_8_950249e1cc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uMSKmoko--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_8_950249e1cc.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_8_950249e1cc.png" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now go to your &lt;em&gt;Terminal&lt;/em&gt; and commit all of the changes that were made in your config files by &lt;em&gt;Strapi&lt;/em&gt;. And then push your changes to rebuild the CMS hosted on Railway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push

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

&lt;/div&gt;



&lt;p&gt;When the app on Railway is rebuilt - log in and change Public user permissions so you'll be able to query Tags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EGEMWLfz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_9_3838f0c00a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EGEMWLfz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_9_3838f0c00a.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531985/Blog_tutorial_2_9_3838f0c00a.png" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can fetch the data in &lt;code&gt;getStaticProps&lt;/code&gt; in &lt;code&gt;index.js&lt;/code&gt; file of your &lt;em&gt;Next.js&lt;/em&gt; app. Add this at the bottom of &lt;code&gt;pages/index.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export async function getStaticProps() {
  const { data } = await client.query({
    query: gql`
      query Posts {
        posts(
          sort: "publishedAt:desc"
          pagination: { limit: 5 }
          filters: { publishedAt: { notNull: true } }
        ) {
          data {
            attributes {
              title
              slug
              cover {
                data {
                  attributes {
                    url
                  }
                }
              }
              excerpt
              tags {
                data {
                  attributes {
                    tagId
                    name
                  }
                }
              }
            }
          }
        }
      }
    `,
  });

  return {
    props: {
      posts: data.posts.data,
    },
  };
}

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

&lt;/div&gt;



&lt;p&gt;What this does? On every build (&lt;code&gt;yarn build&lt;/code&gt;) &lt;em&gt;Next&lt;/em&gt; will perform this query before "rendering" the page/creating the static page output. This means that you will not need any loading indicators on your webpage, because the data will already be fetched. Downside of this is of course that you will have to rebuild the app on every change of content in the CMS. But for personal blogs this is a pretty good and cheap solution.&lt;/p&gt;

&lt;p&gt;You are downloading only the most important data that will be shown on the homepage. So let's just create a simple homepage then.&lt;/p&gt;

&lt;p&gt;You'll start with creating a new component - a card to preview the Blog Post. Create a new file &lt;code&gt;components/BlogPostPreview.jsx&lt;/code&gt; (if there's no &lt;code&gt;component&lt;/code&gt; folder - create it).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Image from "next/image";
import React from "react";

const BlogPostPreview = ({ post }) =&amp;gt; {
  return (
    &amp;lt;div className="max-w-sm rounded overflow-hidden shadow-lg"&amp;gt;
      &amp;lt;a href="#"&amp;gt;
        &amp;lt;Image
          className="w-full"
          src={post.attributes.cover.data.attributes.url}
          alt=""
          width={1000}
          height={480}
          objectFit="cover"
        /&amp;gt;
        &amp;lt;div className="px-6 py-4"&amp;gt;
          &amp;lt;div className="font-bold text-xl mb-2"&amp;gt;{post.attributes.title}&amp;lt;/div&amp;gt;
          &amp;lt;p className="text-gray-700 text-base"&amp;gt;{post.attributes.excerpt}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/a&amp;gt;
      &amp;lt;div className="px-6 pt-4 pb-2"&amp;gt;
        {post.attributes.tags.data.map((tag) =&amp;gt; (
          &amp;lt;a href="#" key={tag.attributes.tagId}&amp;gt;
            &amp;lt;span className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2"&amp;gt;
              {tag.attributes.name}
            &amp;lt;/span&amp;gt;
          &amp;lt;/a&amp;gt;
        ))}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default BlogPostPreview;

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

&lt;/div&gt;



&lt;p&gt;You get one &lt;code&gt;prop&lt;/code&gt; here - post. It has all the fetched information inside. Then we create a simple card with a cover image, title, excerpt and a list of tags. End result will look something like that:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o2sJBddT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531986/Blog_tutorial_2_10_053405e47c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o2sJBddT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531986/Blog_tutorial_2_10_053405e47c.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531986/Blog_tutorial_2_10_053405e47c.png" width="800" height="1076"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see there are some empty &lt;code&gt;a&lt;/code&gt; tags in the code - in the later stages of this tutorial series you'll make a use of them, don't worry.&lt;/p&gt;

&lt;p&gt;Now it's finally the time to showcase your blog posts. In &lt;code&gt;pages/index.js&lt;/code&gt; make use of a newly created component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function Home({ posts }) {
  return (
    &amp;lt;div className="flex flex-col items-center"&amp;gt;
      &amp;lt;h1 className="text-3xl uppercase font-serif font-bold my-8"&amp;gt;
        My personal blog
      &amp;lt;/h1&amp;gt;
      &amp;lt;section className="grid grid-cols-3 gap-4"&amp;gt;
        {posts.map((post) =&amp;gt; (
          &amp;lt;BlogPostPreview post={post} key={post.attributes.slug} /&amp;gt;
        ))}
      &amp;lt;/section&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;One more thing before you build this - if you use &lt;em&gt;Cloudinary&lt;/em&gt; for the images (as was done in the second part of this series) then you will need to add &lt;em&gt;Cloudinary&lt;/em&gt; domain to the &lt;code&gt;next.config.js&lt;/code&gt;. This is needed so you'll be able to use &lt;code&gt;next/image&lt;/code&gt; component instead of &lt;code&gt;img&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fmTrVdyV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531984/Blog_tutorial_2_11_449746a2de.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fmTrVdyV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531984/Blog_tutorial_2_11_449746a2de.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531984/Blog_tutorial_2_11_449746a2de.png" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wow, that was quite a ride. Let's finally build this! Run&lt;br&gt;
&lt;code&gt;yarn build&lt;/code&gt; and wait for this process to finish. If everything goes well - run &lt;code&gt;yarn develop&lt;/code&gt; and open your site in the browser &lt;code&gt;http://localhost:3000/&lt;/code&gt;. You should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zUqp_PyQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531988/Blog_tutorial_2_12_55394fe0c9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zUqp_PyQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531988/Blog_tutorial_2_12_55394fe0c9.png" alt="https://res.cloudinary.com/dvrqjaeju/image/upload/v1661531988/Blog_tutorial_2_12_55394fe0c9.png" width="800" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course don't forget to create a few &lt;em&gt;tags&lt;/em&gt; and &lt;em&gt;posts&lt;/em&gt; (you'll need to create a &lt;em&gt;tag&lt;/em&gt; before you'll be able to use it in a &lt;em&gt;post&lt;/em&gt;). Also as you added some new required fields - update existing posts so they have every needed information filled.&lt;/p&gt;

&lt;p&gt;And that's it. That's the end of the second part of this tutorial series. In the next one you'll create a Blog Post Page. See you then!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>headlesscms</category>
      <category>nextjs</category>
      <category>strapi</category>
    </item>
    <item>
      <title>[Deprecated] Using Cloudinary with Strapi CMS [Building Personal Blog Website Part 2]</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Wed, 02 Mar 2022 10:24:00 +0000</pubDate>
      <link>https://dev.to/hwlkdev/using-cloudinary-with-strapi-cms-building-personal-blog-website-part-2-18o6</link>
      <guid>https://dev.to/hwlkdev/using-cloudinary-with-strapi-cms-building-personal-blog-website-part-2-18o6</guid>
      <description>&lt;p&gt;&lt;strong&gt;As Heroku is phasing out the free tier I'm going to rewrite the first two parts of this guide. Here you can find the updated version: COMING SOON.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the second part of the "Building Personal Blog Website" series. In the series we'll setup a free CMS to hold our blog content on Heroku, create a React app with Next.js' static site generation and TailwindCSS to present the articles and host it on Netlify.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All articles:&lt;br&gt;
&lt;a href="https://dev.to/hawelkam/hosting-free-strapi-cms-on-heroku-building-personal-blog-website-part-1-58gg"&gt;Part 1: Hosting free Strapi CMS on Heroku&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dev.to/hawelkam/using-cloudinary-with-strapi-cms-building-personal-blog-website-part-2-18o6"&gt;Part 2: Using Cloudinary with Strapi CMS&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dev.to/hawelkam/setting-up-a-basic-nextjs-app-locally-building-personal-blog-website-part-3-13e6"&gt;Part 3: Setting up a basic Next.js app locally&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As you are building a personal blog it is really possible that you'd want to use some images in the blog posts. The CMS is hosted on a free-tier Heroku, so the storage is rather limited. That's why you need to think about other solutions. And here's where the cloud comes in.&lt;/p&gt;

&lt;p&gt;For this I have chosen &lt;a href="https://cloudinary.com/" rel="noopener noreferrer"&gt;&lt;em&gt;Cloudinary&lt;/em&gt;&lt;/a&gt;. It's a rather popular platform for managing assets and optimizing images. But the main reason to use it for our personal project is that it has a free tier with easy-to-use API. Config is worry-free - you'll go through it today.&lt;/p&gt;

&lt;p&gt;But before you start - head out to &lt;a href="https://cloudinary.com/" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt;, create a free account (the process is rather standard) and when you're done - log in and get your SDK config (&lt;em&gt;Getting Started -&amp;gt; Configure your SDK -&amp;gt; Start configuring&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg47qibfc233l1c9i1a5d.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%2Fg47qibfc233l1c9i1a5d.png" alt="Cloudinary config"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save those variables somewhere and then let's go back to your Strapi app in your code editor of choice.&lt;/p&gt;

&lt;p&gt;First you need to install a Cloudinary provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add @strapi/provider-upload-cloudinary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update Strapi config in &lt;em&gt;config/plugins.js&lt;/em&gt; file (create it if it doesn't exist):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = ({ env }) =&amp;gt; ({
 upload: {
   config: {
     provider: "cloudinary",
     providerOptions: {
       cloud_name: env("CLOUDINARY_NAME"),
       api_key: env("CLOUDINARY_KEY"),
       api_secret: env("CLOUDINARY_SECRET"),
     },
     actionOptions: {
       upload: {},
       delete: {},
     },
   },
 },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next step would be to create the environment variables through Heroku CLI so they would have proper values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;heroku config:set CLOUDINARY_NAME=[cloud_name from Cloudinary config]
heroku config:set CLOUDINARY_KEY=[api_key from Cloudinary config]
heroku config:set CLOUDINARY_SECRET=[api_secret from Cloudinary config]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're almost done! Now you need to update the middleware for Strapi - &lt;em&gt;strapi::security&lt;/em&gt; to be exact. Instead of just &lt;em&gt;string&lt;/em&gt; you should put an object there (&lt;em&gt;config/middleware.js&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = [
 "strapi::errors",
 {
   name: "strapi::security",
   config: {
     contentSecurityPolicy: {
       useDefaults: true,
       directives: {
         "connect-src": ["'self'", "https:"],
         "img-src": ["'self'", "data:", "blob:", "res.cloudinary.com"],
         "media-src": ["'self'", "data:", "blob:", "res.cloudinary.com"],
         upgradeInsecureRequests: null,
       },
     },
   },
 },
 "strapi::cors",
 "strapi::poweredBy",
 "strapi::logger",
 "strapi::query",
 "strapi::body",
 "strapi::session",
 "strapi::favicon",
 "strapi::public",
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now commit all of you changes and push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push heroku HEAD:main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! After the app is redeployed the CMS will use Cloudinary for storing images. Let's just quickly test this. Head out to your &lt;em&gt;Strapi Admin Panel&lt;/em&gt; on &lt;code&gt;https://[your-app].herokuapp.com/admin/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Go into &lt;em&gt;Content Manager&lt;/em&gt; and edit the blog post you created in the &lt;a href="https://dev.to/hawelkam/hosting-free-strapi-cms-on-heroku-building-personal-blog-website-part-1-58gg"&gt;first part of this article series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5iv2git0g1pgc4ugi3wx.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%2F5iv2git0g1pgc4ugi3wx.png" alt="Edit post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on the area below &lt;code&gt;cover&lt;/code&gt; (or whatever you named this field) and just follow the instructions on the screen to add a new image.&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%2Fcn7defwf2v37dmit8ymg.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%2Fcn7defwf2v37dmit8ymg.png" alt="Adding image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmaitz7zl99b4vnrz4zg0.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%2Fmaitz7zl99b4vnrz4zg0.png" alt="Adding image - step 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After saving you should see in the blog post editor that the image is in fact there.&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%2Fyltxfomnzijtp4w6c2av.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%2Fyltxfomnzijtp4w6c2av.png" alt="Edited blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But now to be entirely sure the image was added correctly - go to the &lt;a href="https://cloudinary.com/console" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt; website, log in and go into &lt;em&gt;Media Library&lt;/em&gt;. There you should see your newly created image in 3-4 sizes.&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%2Fbweyqjf22dc83hur9xa1.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%2Fbweyqjf22dc83hur9xa1.png" alt="Cloudinary"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Easy, right? So now that you are ready to create some blog posts with images you can start working on a React app to display them. And that will be the third part of this guide, so stay tuned!&lt;/p&gt;

</description>
      <category>cloudinary</category>
      <category>strapi</category>
      <category>tutorial</category>
      <category>headlesscms</category>
    </item>
    <item>
      <title>[Deprecated] Hosting free Strapi CMS on Heroku [Building Personal Blog Website Part 1]</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Mon, 21 Feb 2022 12:03:00 +0000</pubDate>
      <link>https://dev.to/hwlkdev/hosting-free-strapi-cms-on-heroku-building-personal-blog-website-part-1-58gg</link>
      <guid>https://dev.to/hwlkdev/hosting-free-strapi-cms-on-heroku-building-personal-blog-website-part-1-58gg</guid>
      <description>&lt;p&gt;&lt;strong&gt;As Heroku is phasing out the free tier I'm going to rewrite the first two parts of this guide. Here you can find the updated version: COMING SOON.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the first part of the "Building Personal Blog Website" series. In the series we'll setup a free CMS to hold our blog content on Heroku, create a React app with Next.js' static site generation and TailwindCSS to present the articles and host it on Netlify.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All articles:&lt;br&gt;
&lt;a href="https://dev.to/hawelkam/hosting-free-strapi-cms-on-heroku-building-personal-blog-website-part-1-58gg"&gt;Part 1: Hosting free Strapi CMS on Heroku&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dev.to/hawelkam/using-cloudinary-with-strapi-cms-building-personal-blog-website-part-2-18o6"&gt;Part 2: Using Cloudinary with Strapi CMS&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dev.to/hawelkam/setting-up-a-basic-nextjs-app-locally-building-personal-blog-website-part-3-13e6"&gt;Part 3: Setting up a basic Next.js app locally&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Whether you want to create a simple blog or a portfolio website it is always a good idea to use a good CMS (Content Management System) behind. For quite some time Wordpress was a go-to solution, but now the choice is not so obvious. As an ambitious developer you can of course try to write your own CMS, but let's be frank - in most cases the best way is to use an existing one. That was my approach when I started to work on my personal website. &lt;/p&gt;

&lt;p&gt;My main interest were as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ability to change website's content without changing the code&lt;/li&gt;
&lt;li&gt;simple blog functionality&lt;/li&gt;
&lt;li&gt;out-of-the box GraphQL integration&lt;/li&gt;
&lt;li&gt;cheap site maintenance (preferably free)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After some time of "research" I chose Strapi CMS for the first three points and Heroku for the last one.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Strapi?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It's lightweight - compared to other CMSs Strapi is lightweight and really simple to set up.&lt;/li&gt;
&lt;li&gt;It's headless by design - I definitely prefer a headless CMS solution over a traditional CMS, it gives much more flexibility.&lt;/li&gt;
&lt;li&gt;It's written in JavaScript - this definitely helps with writing extensions/plugins if you use JS on a daily basis (which I am).&lt;/li&gt;
&lt;li&gt;It has really extensive docs - Strapi docs helped me in tons of situations, if you have an problem with Strapi it's almost guaranteed you'll find some help in the docs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why headless?
&lt;/h2&gt;

&lt;p&gt;Main reason - it's technology agnostic - traditional CMS requires you to write templates etc. in the technology the CMS is written. With headless this problem is gone. You utilize the REST/GraphQL endpoints to fetch the data and display it however you like.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to set this up?
&lt;/h2&gt;

&lt;p&gt;For the most part I used a guide from &lt;a href="https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.html" rel="noopener noreferrer"&gt;Strapi Docs&lt;/a&gt;. This guide shows how to create a new Heroku app with Strapi installed and connect it to the free-tier postgres database. I won't go into details here and repeat what's been said in this guide. But I have just a few additions to make it work properly.&lt;/p&gt;

&lt;p&gt;So for starters go through &lt;a href="https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.html" rel="noopener noreferrer"&gt;the guide&lt;/a&gt; - but don't worry if at the end it doesn't work on Heroku (you get the application error).&lt;/p&gt;

&lt;p&gt;The error says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: Middleware "strapi::session": App keys are required. 
Please set app.keys in config/server.js (ex: keys: ['myKeyA', 'myKeyB'])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's why you need to make adjustments to production config. In the production config file created during the guide earlier (&lt;em&gt;env/production/server.js&lt;/em&gt;) add few additional fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = ({ env }) =&amp;gt; ({
  url: env("MY_HEROKU_URL"),
  proxy: true,
  app: {
    keys: env.array("APP_KEYS", ["testKey1", "testKey2"]),
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;app.keys&lt;/code&gt; is crucial. The data that you put in server.js doesn't matter. What matters is the value that you set in Heroku environment. You can do it directly through Heroku CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;heroku config:set APP_KEYS=someSecretValue1,someSecretValue2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you commit and push changes and redeploy the app you should be able to see that Strapi server is running. Go to &lt;code&gt;https://[yourapp].herokuapp.com/admin&lt;/code&gt; to set up the admin user and then log in. You should land in the dashboard.&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%2Fodzz6ufwqhsgs4tlk4u4.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%2Fodzz6ufwqhsgs4tlk4u4.png" alt="Strapi Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the app is in production mode you can't really make any adjustments here. Of course you can add a new post etc. but can't create a new content-type or add a new plugin. That's why you need to go into developer mode - make changes there and redeploy the app.&lt;/p&gt;

&lt;p&gt;Doing this is pretty simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn develop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when the app starts you'll need to set up admin account once again (it's stored in SQlite database, so it'll be gone after some time locally - but that's not a problem). Now when you log in you can create new content-types, configure plugins etc. &lt;/p&gt;

&lt;p&gt;Before starting creating content-types let's just quickly install a plugin that will be really useful later - GraphQL plugin which enables the default GraphQL endpoint for your CMS (you can skip it if you're ok with using standard REST calls, but I'll be using GraphQL in the React app). You can install it easily using yarn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add @strapi/plugin-graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that out of the way let's just create a blog post type.&lt;/p&gt;

&lt;p&gt;In the content-type builder select a "&lt;em&gt;Create a new collection type&lt;/em&gt;". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84vh44ttfw0eotu3pfpe.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%2F84vh44ttfw0eotu3pfpe.png" alt="New collection type"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And add some mandatory fields to it. In my case it's &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;cover&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt; and &lt;code&gt;slug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F282miyt1vo4whz5jlcqs.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%2F282miyt1vo4whz5jlcqs.png" alt="Post fields"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;author&lt;/code&gt; it's important to select a proper relation:&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%2Fsfdcrwheu64q2bg3vp0w.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%2Fsfdcrwheu64q2bg3vp0w.png" alt="User to Post relation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For slug remember to make it of type &lt;code&gt;UID&lt;/code&gt; and select &lt;code&gt;title&lt;/code&gt; as an attached field:&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%2Fujouh8cd8d0j0324dfgw.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%2Fujouh8cd8d0j0324dfgw.png" alt="Slug config"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you save you can commit all the new files - the schema files etc. will tell the production app what kind of content should be possible to create.&lt;/p&gt;

&lt;p&gt;Now you are ready to redeploy the app using &lt;em&gt;Heroku CLI&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push heroku HEAD:main 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the deploy is done you can open the app with &lt;code&gt;heroku open&lt;/code&gt;, log in with the admin credentials and do some final configuration.&lt;/p&gt;

&lt;p&gt;What you need to do is to give public access to posts. Do this by going into &lt;em&gt;Settings -&amp;gt; Users &amp;amp; Permissions Plugin -&amp;gt; Public -&amp;gt; Edit&lt;/em&gt; (icon).&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%2F1mb0bpmzwjpkh3y3i14j.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%2F1mb0bpmzwjpkh3y3i14j.png" alt="Permissions editing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While inside, give access to &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;findOne&lt;/code&gt; for &lt;code&gt;posts&lt;/code&gt;. This way you'll be able to query the CMS for the list of blog posts and for a specific one post (using &lt;code&gt;slug&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6q1gc35z4odush9ko197.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%2F6q1gc35z4odush9ko197.png" alt="Posts permission"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;em&gt;Save&lt;/em&gt; and now let's create an author and a dummy blog post to test the endpoint (&lt;em&gt;Content Manager -&amp;gt; User -&amp;gt; Create New Entry&lt;/em&gt; and &lt;em&gt;Content Manager -&amp;gt; Post -&amp;gt; Create New Entry&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsodque1bnpvdlu76fzcn.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%2Fsodque1bnpvdlu76fzcn.png" alt="Creating new post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After saving a post you also need to publish it so it would be visible in the API.&lt;/p&gt;

&lt;p&gt;To check if everything works use &lt;em&gt;Postman&lt;/em&gt; (or a similar app). &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%2Flu0jugeyeegh4qjkd125.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%2Flu0jugeyeegh4qjkd125.png" alt="Postman"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open up Postman and in the address field put &lt;code&gt;https://[yourapp].herokuapp.com/graphql/&lt;/code&gt; and select &lt;em&gt;POST&lt;/em&gt; type of query. Then in the body of the query select &lt;em&gt;graphql&lt;/em&gt; type and type this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query {
    posts {
        data {
            attributes {
                title
                content
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you send this request, you'll receive the blog post that you just created a minute ago:&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%2Fxgf01cpaydfg8akoide3.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%2Fxgf01cpaydfg8akoide3.png" alt="GraphQL response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see the content is returned as markdown - it's much more efficient to send the data this way, but in our frontend app we'll need to convert it to HTML. We'll probably use something like &lt;a href="https://github.com/showdownjs/showdown" rel="noopener noreferrer"&gt;Showdown&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And that's it! We now have a hosted Strapi CMS that is accessible by both REST and GraphQL. Next step - hooking up Cloudinary to our CMS!&lt;/p&gt;

</description>
      <category>headlesscms</category>
      <category>strapi</category>
      <category>heroku</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How React relit my programming passion</title>
      <dc:creator>Michał Hawełka</dc:creator>
      <pubDate>Sun, 11 Apr 2021 17:32:54 +0000</pubDate>
      <link>https://dev.to/hwlkdev/how-react-relit-my-programming-passion-2nej</link>
      <guid>https://dev.to/hwlkdev/how-react-relit-my-programming-passion-2nej</guid>
      <description>&lt;p&gt;My IT career went from full-on backend C++ developer to full-on frontend enthusiast in a matter of few years. I searched for my IT ‘Holy Grail’ for quite some time, but it looks like I finally found it. Disclaimer: this article is totally subjective and emotion-based, but I’ll try not to bend the facts ;)&lt;/p&gt;

&lt;h1&gt;
  
  
  How it all came to be
&lt;/h1&gt;

&lt;p&gt;Let’s start with a bit of my background (if you want to skip that part — just go straight to React part).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KNzWS0HS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ys89lx8mv21xery47knm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KNzWS0HS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ys89lx8mv21xery47knm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started my IT career in 2013 with a C++ student internship. It was the only programming language I knew at the time (it was used in my university courses). To be honest — I approached the field with rather cynical mindset — I liked to program, but it wasn’t my favorite thing in the world. My main motivation was to earn lots of money (you know how IT is sold by universities). But after few years of C++ programming I felt that’s not the thing I want to do in my life. I started looking for other fields/technologies and my focus turned to more “frontend” and “client-oriented” approach. That’s why in the following years I had an “adventure” with being a Product Owner and then switched to not-as-backend-as-C++ Java. I was working with it for a few years, but with every working day I got more tired of it. 2-3 years ago I can say that my programming passion died out.&lt;/p&gt;

&lt;p&gt;2 years ago I moved to a new company, where my responsibilities wouldn’t be only in backend, but also in frontend. I started learning AngularJS and Angular — definitely liked it more than writing backend code, but still some parts of those made me uncomfortable. That’s when I looked React’s way. And that was a bullseye!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---sASoUs6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xe5a0thk2wh51t3wxs0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---sASoUs6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xe5a0thk2wh51t3wxs0g.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  React is the way
&lt;/h1&gt;

&lt;p&gt;I was interested in learning React for quite some time. Given that it powers a lot of apps I use on a daily basis, the idea to try to use it by myself arose. Unfortunately with my tendency to procrastination this idea was always just out of reach. The turning point was (oh, irony) the pandemic. Lots of websites started to offer their courses for a reduced price or even free. And that’s how I got to Udacity and their React Nanodegree. I decided to challenge myself and complete whole program in a free month. Spoiler alert — the challenge worked!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NTayFB_Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dede9jrzz1q56npt59td.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NTayFB_Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dede9jrzz1q56npt59td.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Udacity’s React Nanodegree consists of 3 main subjects — React Fundamentals, React with Redux and React Native. All of those were new to me, but now I can say I’m able to use those (of course with documentation’s help) rather freely. Also during the nanodegree I had to complete 3 projects, but two of those gave me a lot — the second project taught me a lot about React Redux and the third one about React Native.&lt;/p&gt;

&lt;h1&gt;
  
  
  React Redux project — Polls App
&lt;/h1&gt;

&lt;p&gt;What I liked most in the React Redux project is that the course gave the user only the ‘backend’ API, the rest had to be written by the user. I started with drawing all of the views for the app, then determining which data will need to be stored in the redux store and which directly in component’s state. Then I prepared some mock components and routes just to have a general idea of the app (it wasn’t doing anything then). This is where redux came into play. I wrote actions and reducers as was taught in the course. When the app started to actually do something I got in some kind of trance. I lost track of time while writing code, I thought about the project even when I wasn’t working on it. That was the first time I felt it since… ever.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TZCKhDyC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jmhauif96e403913cglj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TZCKhDyC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jmhauif96e403913cglj.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app was just a simple polls app where user entered two options and then rest of the users could vote on option 1 or 2. And even though it was that simple — I was 100% invested in it. Now that I passed the nanodegree and the project was accepted — I still want to extend this app. I think about major refactor, because I’m aware that the code is not really ‘nice’. It was written fast (because of the challenge I gave myself), but I want it to become ‘proper’.&lt;/p&gt;

&lt;p&gt;Here you can see the code I wrote (every comment is appreciated, I want to learn from experienced frontend professionals): &lt;a href="https://github.com/hawelkam/polls-app"&gt;https://github.com/hawelkam/polls-app&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  React Native project — Mobile Flashcards
&lt;/h1&gt;

&lt;p&gt;Third and final project in the nanodegree was React Native app called ‘Mobile Flashcards’. It was the first mobile app that I fully created by myself. I tried Swift and Flutter before, but those felt a little bit to ‘backendy’ for me. Native approach suits me really well. I just use the ideas that I learned during web app development and used them here. The process was similar — I started with drawing views, preparing redux store data scheme and building a skeleton of the app. With experiences from the previous project this went much faster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wJpirDHA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/93o171x15lf9tvv7mbr9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wJpirDHA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/93o171x15lf9tvv7mbr9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But as the content from nanodegree isn’t necessarily up to date (React Native course uses code with lots of deprecated stuff) I had to dive deep into docs. And that was a great learning experience. I learned about Navigators, styling and Notifications — something that was shown in the course, but unfortunately in deprecated versions. The app saves data to AsyncStorage, lets user create the cards deck, add new questions to it and quiz him/herself. Right now it’s a very simple app, but, just like with react redux project, I’m going to extend the functionalities. Work more on the UX/UI of the app. Maybe integrate it with some NoSQL database. I have lots of ideas how to make this app better. And again — it’s something I haven’t felt since…ever.&lt;br&gt;
Here you can see the code I wrote (every comment is appreciated, I want to learn from experienced frontend professionals): &lt;a href="https://github.com/hawelkam/mobile-flashcards"&gt;https://github.com/hawelkam/mobile-flashcards&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  What I like in React
&lt;/h1&gt;

&lt;p&gt;Am I the first one to say ‘JSX’?&lt;br&gt;
I really like how the code written in JSX looks. It can get a little bit messy, but if you follow React’s philosophy of making everything a component— the JSX code looks clean. You know where to look for specific code even on the first glimpse of App.js. That’s something I really like in React.&lt;br&gt;
Other thing I like is modularity. In plain HTML I needed to copy/paste a lot of the code to get same results, in Angular it’s better, but still a little bit too complicated for my taste, but in React you just create a functional component and use it everywhere you want. That is great especially when you attach styling to this component from the start — then you don’t need to repeat yourself not only with the code, but also with styles.&lt;br&gt;
Third and final ‘big’ thing I like about React is its simplicity. Getting up to speed with is is extremely easy, especially if you have worked with any other JS framework before. It’s from the ‘easy to learn, hard to master’ category and that’s great. It allows me to learn constantly when writing something new. I always come across a technique I never used before and adapting them is a piece of cake. Of course to be the React master I’d need hundreds of hours of coding experience, but I’m optimistic I’ll get there one day!&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s next
&lt;/h1&gt;

&lt;p&gt;I think learning React is the catalyst for my full switch to frontend developer. After finishing the course I wrote one more app — this time based on the Ionic framework and after that — I started working on my portfolio website (fully created with React and react-bootstrap). I see a lot of things I want to dive deeper into like React Hooks, Styled Components, Webpack etc. and that will lead my way forward. In the meantime I moved to another company where I was hired as a full-stack developer, but after discussing my priorities with my manager I got assigned to the project where I work as a frontend developer with Angular. So i'm one step closer to my final goal ;)&lt;/p&gt;

&lt;h1&gt;
  
  
  The last cycle begins (?)
&lt;/h1&gt;

&lt;p&gt;The experiences I’ve already had with React showed me that this is the thing I want to do. Not just for work, but also in my spare time. I have a lot of fun coding and I guess that is one of the main reasons we should do it (others being more ‘material’ ;)). From time to time I apply to some frontend positions (with React) to check if I’m ready for this kind of work now or not. If not — then I’ll be wiser about what to learn — and that is a great learning experience as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final note
&lt;/h3&gt;

&lt;p&gt;This post was published some time ago on my Medium account, but I updated it a bit and thought this would be a great 'starter' article for my blog here, I hopy you won't mind :)&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>react</category>
      <category>career</category>
      <category>devjournal</category>
    </item>
  </channel>
</rss>
