<?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: James Wallis</title>
    <description>The latest articles on DEV Community by James Wallis (@jameswallis).</description>
    <link>https://dev.to/jameswallis</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%2F436375%2F78f018b6-c23b-4367-9283-9dab09bb935a.jpg</url>
      <title>DEV Community: James Wallis</title>
      <link>https://dev.to/jameswallis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jameswallis"/>
    <language>en</language>
    <item>
      <title>SkylarkTV: Streaming Platform Built with Next.js and Skylark CMS</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Sat, 12 Jul 2025 16:09:32 +0000</pubDate>
      <link>https://dev.to/jameswallis/skylarktv-streaming-platform-built-with-nextjs-and-skylark-cms-18gh</link>
      <guid>https://dev.to/jameswallis/skylarktv-streaming-platform-built-with-nextjs-and-skylark-cms-18gh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;After I left Skylark and the project was discontinued after 2-3 years of development, I was able to fork SkylarkTV, our demo application, to use a portfolio piece. There was one snag - the Skylark server no longer exists... Rather than let this substantial engineering effort go to waste, I partnered with Claude AI to analyze the existing GraphQL queries and create a comprehensive mock system. By examining the query patterns, response structures, and business logic embedded in the frontend, we successfully recreated the entire API surface using Mock Service Worker (MSW). This transformation turned what could have been a dead project into a living portfolio piece that demonstrates the same complex functionality without requiring expensive backend infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;SkylarkTV is a mock streaming television platform that demonstrates the full capabilities of Skylark, a headless CMS designed for world-class streaming products. This project showcases advanced content management, complex availability systems, and production-quality frontend architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href="https://skylarktv.wallis.dev" rel="noopener noreferrer"&gt;skylarktv.wallis.dev&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Source Code&lt;/strong&gt;: &lt;a href="https://github.com/skylark-platform/skylarktv" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Tech Stack&lt;/strong&gt;: Next.js, TypeScript, Tailwind CSS, React Query, MSW, Skylark&lt;/p&gt;
&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Building a streaming platform requires handling complex business logic that goes far beyond simple content display. The application needed to support and demonstrate Skylark features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-dimensional content availability&lt;/strong&gt; (time, region, device, customer type)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time-travel functionality&lt;/strong&gt; for viewing content at specific points in time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internationalization&lt;/strong&gt; with dynamic language switching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex content relationships&lt;/strong&gt; (brands → seasons → episodes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time availability filtering&lt;/strong&gt; based on sophisticated business rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise-scale content management&lt;/strong&gt; with thousands of media objects&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Key Achievements
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Mock System Architecture
&lt;/h3&gt;

&lt;p&gt;When the original Skylark backend was discontinued, I architected a comprehensive MSW-based mocking system that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preserves all original functionality without requiring expensive backend infrastructure&lt;/li&gt;
&lt;li&gt;Handles complex GraphQL queries with proper relationship resolution&lt;/li&gt;
&lt;li&gt;Maintains data consistency across related content types&lt;/li&gt;
&lt;li&gt;Enables portfolio demonstration without ongoing operational costs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Content Management Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent search&lt;/strong&gt; across movies, episodes, brands, articles, and people&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic content highlighting&lt;/strong&gt; with search term emphasis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sophisticated filtering&lt;/strong&gt; by availability dimensions and time constraints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive design&lt;/strong&gt; optimized for desktop, tablet, and mobile devices&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Technical Highlights
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Availability Rules Engine
&lt;/h3&gt;

&lt;p&gt;The platform includes a sophisticated rules engine that processes complex availability logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time-based access windows&lt;/li&gt;
&lt;li&gt;Geographic restrictions&lt;/li&gt;
&lt;li&gt;Device-specific content&lt;/li&gt;
&lt;li&gt;Customer tier limitations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In SkylarkTV, Customer Types demonstrate the most dramatic impact on content availability. In the screenshot below, you can see three different views of the same homepage: Premium (left), Standard (middle), and Kids (right). Notice the subtle difference between Premium and Standard users - a single rail is removed - while the Kids experience is completely transformed with age-appropriate content and a simplified interface.&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%2Fugllm6q8gtrdkvjmt6m9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fugllm6q8gtrdkvjmt6m9.png" alt="Customer Type Comparison - Premium vs Standard vs Kids" width="800" height="999"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Complex Relationship Management
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example: Brand → Season → Episode hierarchy&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;brand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getBrand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;brandId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSeasonsForBrand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;brandId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;episodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getEpisodesForSeason&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seasonId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The platform handles complex content relationships while maintaining performance through intelligent caching and lazy loading.&lt;/p&gt;
&lt;h3&gt;
  
  
  Multi-language Support
&lt;/h3&gt;

&lt;p&gt;Content can be dynamically localized with proper fallback handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localizedContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mergeTranslatedContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;baseContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;languageCode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;p&gt;In the future I want to use SkylarkTV to validate whether with thorough testing, developers can merge dependency upgrades without worrying about breaking changes. The current plan is to create Playwright tests across the whole application, configure Renovate in the repository and allow automatic pull request merging when the build passes.&lt;/p&gt;

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

&lt;p&gt;SkylarkTV represents a sophisticated example of modern web development, combining advanced frontend techniques with complex business logic. The project demonstrates the ability to work with enterprise-scale content management systems while maintaining excellent user experience and developer productivity.&lt;/p&gt;

&lt;p&gt;The transition from a live backend to a comprehensive mock system showcases problem-solving skills and architectural thinking - turning a cancelled project into a valuable portfolio piece that continues to demonstrate technical capabilities without ongoing infrastructure costs.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>frontend</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Using Composite GitHub Actions to make your Workflows smaller and more reusable</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Wed, 08 Dec 2021 14:29:41 +0000</pubDate>
      <link>https://dev.to/jameswallis/using-github-composite-actions-to-make-your-workflows-smaller-and-more-reusable-476l</link>
      <guid>https://dev.to/jameswallis/using-github-composite-actions-to-make-your-workflows-smaller-and-more-reusable-476l</guid>
      <description>&lt;p&gt;At work there has been a drive to move existing CI/CD pipelines from Jenkins to GitHub Workflows in order to empower developers with more control over test, feature and production deployments. &lt;/p&gt;

&lt;p&gt;In addition, we've recently moved to a monorepo structure and are building new workflows that deploy multiple applications at once. A result of using a monorepo structure is that the GitHub Workflows vary in size and purpose. For example, some carry out simple tasks such as unit testing an individual component whereas other, larger workflows deploy full production environments. &lt;/p&gt;

&lt;p&gt;This post aims to demonstrate how composite GitHub Actions can be used to to split workflows into smaller, reusable components.&lt;/p&gt;




&lt;h2&gt;
  
  
  Local GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Before discussing Composite actions, we should first talk about local GitHub Actions. Generally, when you're creating GitHub Workflows you will be using actions found on the &lt;a href="https://github.com/marketplace?type=actions" rel="noopener noreferrer"&gt;Marketplace&lt;/a&gt; but you don't have to publish to the Marketplace to use your own. Did you know you can &lt;code&gt;use&lt;/code&gt; local GitHub Actions stored in your repository?.&lt;/p&gt;

&lt;p&gt;Local actions are well suited to componentized GitHub Workflows as they can be maintained alongside other infrastructure code stored in the same repository. It's a good practice to store them next to the GitHub Workflow's &lt;code&gt;.github/workflows&lt;/code&gt; directory in &lt;code&gt;.github/actions&lt;/code&gt; but in reality they can be located anywhere in the repository. &lt;/p&gt;

&lt;p&gt;Each custom action requires its own directory and &lt;code&gt;action.yml&lt;/code&gt; to define it. Once the &lt;code&gt;action.yml&lt;/code&gt; is created you can add the &lt;a href="https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions" rel="noopener noreferrer"&gt;standard metadata&lt;/a&gt; to define how the action will operate.&lt;/p&gt;

&lt;p&gt;When you're ready to use your local action in a workflow, use the following syntax (assuming you've stored your custom actions in the &lt;code&gt;.github/actions&lt;/code&gt; directory).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;run-local-action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Checkout the repository first&lt;/span&gt;
      &lt;span class="c1"&gt;# Otherwise the workflow won't be able to find the action&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run custom action&lt;/span&gt;
      &lt;span class="c1"&gt;# Use the location in the repository (without action.yml)&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/actions/my-custom-action&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;custom-input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local actions are a great way to create GitHub actions that are tailored to how you want them to operate without having to manage yet another product release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composite Actions
&lt;/h2&gt;

&lt;p&gt;A Composite action is one of &lt;a href="https://docs.github.com/en/actions/creating-actions/about-custom-actions" rel="noopener noreferrer"&gt;three different types custom GitHub Actions&lt;/a&gt; that can be created (composite, JavaScript and Docker). The main difference is that a composite action's &lt;code&gt;action.yml -&amp;gt; runs&lt;/code&gt; property contains a list of steps to run as opposed to a program to execute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;composite"&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;steps&lt;/code&gt; section acts almost exactly the same as the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idsteps" rel="noopener noreferrer"&gt;&lt;code&gt;steps&lt;/code&gt; section in a workflow&lt;/a&gt;, with a few differences that are outlined later.&lt;/p&gt;

&lt;p&gt;Creating a local, composite GitHub Action looks like the perfect way to split up GitHub Workflows!&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you split your workflows up into composite actions?
&lt;/h2&gt;

&lt;p&gt;Having utilised composite actions for a few weeks I believe you absolutely should use them as they have led to smaller, more readable workflows as each action has a specific purpose.&lt;/p&gt;

&lt;p&gt;Below I've added some advantages and limitations of using composite actions in your workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Separate large workflows into multiple files&lt;/li&gt;
&lt;li&gt;Create componentized actions to be used in multiple workflows - reducing duplication&lt;/li&gt;
&lt;li&gt;Many steps are condensed into a single one within the &lt;code&gt;Actions&lt;/code&gt; view on GitHub, improving the ability to track a workflows progress&lt;/li&gt;
&lt;li&gt;The descriptive nature of an &lt;code&gt;action.yml&lt;/code&gt; file improves the readability of a GitHub workflow when understanding necessary inputs and outputs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
   Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;They can't read GitHub Secrets - you have to pass them in&lt;/li&gt;
&lt;li&gt;Have to define the &lt;code&gt;shell&lt;/code&gt; on each step - although this is more of a minor annoyance.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Imagine you have a GitHub Workflow that deploys a Node.js/Express API &lt;em&gt;somewhere&lt;/em&gt; and a React application to AWS S3. It will be stored at &lt;code&gt;.github/workflows/deploy-app.yml&lt;/code&gt; in your repository and would look something like the below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy app&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy-api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Output the deployed API URL for consumption later&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deploy.output.url }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="c1"&gt;# ...more steps to deploy an API and output a URL&lt;/span&gt;
  &lt;span class="na"&gt;deploy-frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy frontend to AWS S3&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-api&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Always store tokens in a secret manager. Here I am using GitHub Secrets&lt;/span&gt;
          &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-west-1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build app&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;APP_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Demo&lt;/span&gt;
          &lt;span class="na"&gt;API_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ needs.deploy-api.outputs.url }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload app to AWS S3&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 sync build s3://my-bucket --delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this workflow itself is relatively small and readable, imagine if you added additional apps to deploy or simply a more complicated frontend deployment - it would likely become bloated and much more difficult to read.&lt;/p&gt;

&lt;p&gt;To future-proof this workflow, we can consolidate much of the &lt;code&gt;frontend-deploy&lt;/code&gt; job into a composite action:&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the custom action file
&lt;/h3&gt;

&lt;p&gt;First we create the directory and &lt;code&gt;action.yml&lt;/code&gt;. As I said previously, it's consistent to store custom actions in a &lt;code&gt;.github/actions&lt;/code&gt; directory. So my &lt;code&gt;deploy-frontend-to-s3&lt;/code&gt; action will be stored as &lt;code&gt;.github/actions/deploy-frontend-to-s3/action.yml&lt;/code&gt; giving us the repository structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.github
  - workflows
    - deploy-app.yml
  - actions
    - deploy-api/action.yml
    - deploy-frontend-to-s3/action.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementing the &lt;code&gt;deploy-frontend-to-s3&lt;/code&gt; composite action
&lt;/h3&gt;

&lt;p&gt;Now that the &lt;code&gt;action.yml&lt;/code&gt; file is created we can migrate many of the &lt;code&gt;deploy-frontend&lt;/code&gt; job's steps out of the workflow and into it.&lt;/p&gt;

&lt;p&gt;First we can add the &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;inputs&lt;/code&gt; to the &lt;code&gt;action.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Frontend&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;S3"&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Builds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deploys&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;React&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;frontend&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AWS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;S3"&lt;/span&gt;
&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
     &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;used&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;authenticate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AWS"&lt;/span&gt;
   &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
     &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;used&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;authenticate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AWS"&lt;/span&gt;
   &lt;span class="na"&gt;app-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
     &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;used&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;React&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app"&lt;/span&gt;
     &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Demo&lt;/span&gt;
   &lt;span class="na"&gt;api-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
     &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;URL&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Express&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we can create the part of the action which will run the commands previously located in the workflow. Note how we can refer to the inputs above with &lt;code&gt;${{ inputs.input-name }}&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ...name, description and inputs as above&lt;/span&gt;
&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;composite"&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Actions cannot access secrets so pass them in as inputs&lt;/span&gt;
        &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.aws-access-key-id }}&lt;/span&gt;
        &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.aws-secret-access-key }}&lt;/span&gt;
        &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-west-1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build app&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;APP_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.app-name }}&lt;/span&gt;
        &lt;span class="na"&gt;API_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.api-url }}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload app to AWS S3&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 sync build s3://my-bucket --delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the &lt;code&gt;steps&lt;/code&gt; section is almost a copy and paste from the &lt;code&gt;steps&lt;/code&gt; section of the workflow. The only part missing is the &lt;code&gt;actions/checkout&lt;/code&gt; as it is required to be in the workflow so that GitHub can access the local action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the custom actions in the &lt;code&gt;deploy-app&lt;/code&gt; workflow
&lt;/h3&gt;

&lt;p&gt;Now that we've built the custom action, we can use it in our workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy app&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy-api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Output the deployed API URL for consumption later&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deploy.output.url }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
         &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/actions/deploy-api&lt;/span&gt;
  &lt;span class="na"&gt;deploy-frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy frontend to AWS S3&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-api&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy frontend&lt;/span&gt;
        &lt;span class="c1"&gt;# Directory name only&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/actions/deploy-frontend-to-s3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_KEY }}&lt;/span&gt;
          &lt;span class="c1"&gt;# won't pass in app-name as it's defaulted to "Demo"&lt;/span&gt;
          &lt;span class="na"&gt;api-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ needs.deploy-api.outputs.url }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we're done! We've reduced the size of the &lt;code&gt;deploy-app&lt;/code&gt; workflow and made a reusable composite action to deploy the frontend to S3.&lt;/p&gt;

&lt;h3&gt;
  
  
   Using files in the action directory
&lt;/h3&gt;

&lt;p&gt;While building a composite action you may want to create utility files which are consumed by the &lt;code&gt;action.yml&lt;/code&gt;. When doing this you should refer to them using the &lt;code&gt;${{ github.action_path }}&lt;/code&gt; variable so that if you relocate the action, you will not need to update any paths.&lt;/p&gt;

&lt;p&gt;So for a file named &lt;code&gt;.github/actions/deploy-frontend-to-s3/fetch-bucket-name.sh&lt;/code&gt;, you can refer to it as &lt;code&gt;${{ github.action_path }}/fetch-bucket-name.sh&lt;/code&gt; in the action.&lt;/p&gt;




&lt;h2&gt;
  
  
  Round up
&lt;/h2&gt;

&lt;p&gt;In this post I have demonstrated how composite actions can help to break a GitHub Workflow into smaller, consumable components and explained why its advantageous to do so.&lt;/p&gt;

&lt;p&gt;Do you deal with huge workflows at work or are you already using composite actions? Let me know in the comments below.&lt;/p&gt;

&lt;p&gt;If this article has helped you, drop a reaction!&lt;/p&gt;

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

</description>
      <category>github</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>Animating Next.js page transitions with Framer Motion</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Mon, 05 Jul 2021 21:18:44 +0000</pubDate>
      <link>https://dev.to/jameswallis/animating-next-js-page-transitions-with-framer-motion-1g9j</link>
      <guid>https://dev.to/jameswallis/animating-next-js-page-transitions-with-framer-motion-1g9j</guid>
      <description>&lt;p&gt;A few months ago &lt;a href="https://dev.to/jameswallis/i-completely-rewrote-my-personal-website-using-dev-to-as-a-cms-2pje"&gt;I rebuilt my Dev.to powered Next.js website&lt;/a&gt; from scratch. While building it, I decided adding animations would bring its simple design to life. Previously, I'd used CSS transitions and JavaScript to achieve animations on a webpage. This time I wanted to use an animation library built for React.js that I could use in future projects. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Enter Framer Motion.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Framer Motion
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A production-ready motion library for React.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;- &lt;a href="https://www.framer.com/motion" rel="noopener noreferrer"&gt;https://www.framer.com/motion&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's a library that enables the animations of React components on a page and while the component is entering and also leaving.&lt;/p&gt;

&lt;p&gt;Framer Motion can do all of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring animations&lt;/li&gt;
&lt;li&gt;Simple keyframes syntax&lt;/li&gt;
&lt;li&gt;Gestures (drag/tap/hover)&lt;/li&gt;
&lt;li&gt;Layout and shared layout animations&lt;/li&gt;
&lt;li&gt;SVG paths&lt;/li&gt;
&lt;li&gt;Exit animations&lt;/li&gt;
&lt;li&gt;Server-side rendering&lt;/li&gt;
&lt;li&gt;Variants for orchestrating animations across components&lt;/li&gt;
&lt;li&gt;CSS variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And can bring a static page to life:&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%2Fuy6922sbnmec4lsg0qvq.gif" 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%2Fuy6922sbnmec4lsg0qvq.gif"&gt;&lt;/a&gt;&lt;br&gt;Various animations powered by Framer Motion
  &lt;/p&gt;

&lt;p&gt;Read more about Framer Motion and view examples on &lt;a href="https://www.framer.com/motion/" rel="noopener noreferrer"&gt;their website&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Animating Next.js page transitions
&lt;/h2&gt;

&lt;p&gt;As well as making user triggered animations, Framer Motion can animate a component when it is mounting (entering) and unmounting (leaving). I use this capability to animate the components that come and go when the page changes. In Next.js terms, this is everything apart from &lt;a href="https://nextjs.org/docs/advanced-features/custom-app" rel="noopener noreferrer"&gt;&lt;code&gt;_app.js&lt;/code&gt;&lt;/a&gt; - so all pages and other components. Where possible, using &lt;code&gt;_app.js&lt;/code&gt; to persist layouts between page changes will reduce the amount of rendering that React has to do each time the page changes - potentially improving your app performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparing the codebase
&lt;/h3&gt;

&lt;p&gt;Before I added any animations to my website I did two pieces of refactoring:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Moved common components that shouldn't animate on every page change into &lt;code&gt;_app.js&lt;/code&gt;&lt;/strong&gt;. In my case this meant moving the &lt;code&gt;Header&lt;/code&gt; and &lt;code&gt;Footer&lt;/code&gt; which you can &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/pages/_app.tsx#L57-L65" rel="noopener noreferrer"&gt;see on GitHub&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Added a wrapper component to control the animation states within pages&lt;/strong&gt;. On my website it is the &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/components/Layout.tsx#L20" rel="noopener noreferrer"&gt;&lt;code&gt;Layout&lt;/code&gt;&lt;/a&gt; component. Note the &lt;code&gt;&amp;lt;motion.main&amp;gt;&lt;/code&gt; component which is specific to Framer Motion. In the rendered HTML output this will be a HTML &lt;code&gt;main&lt;/code&gt; element, however, adding the &lt;code&gt;motion.&lt;/code&gt; supplied by Framer Motion provides the ability to pass certain animation props such as &lt;code&gt;transition&lt;/code&gt;, &lt;code&gt;initial&lt;/code&gt; and &lt;code&gt;animate&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
   Entry animations
&lt;/h3&gt;

&lt;p&gt;Looking at the &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/components/Layout.tsx" rel="noopener noreferrer"&gt;&lt;code&gt;Layout&lt;/code&gt;&lt;/a&gt; component you will see an object named &lt;code&gt;variants&lt;/code&gt; (see below). Variants promote cleaner code by removing the requirement to add the animation object to the &lt;code&gt;motion.main&lt;/code&gt; component. You can read more about them on &lt;a href="https://www.framer.com/api/motion/animation/#variants" rel="noopener noreferrer"&gt;the Framer Motion website&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&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="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&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="na"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;x&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="na"&gt;y&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="na"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&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="na"&gt;x&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="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;100&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;Now focussing on the &lt;code&gt;motion.main&lt;/code&gt; component:&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;motion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Pass the variant object into Framer Motion &lt;/span&gt;
    &lt;span class="na"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="c1"&gt;// Set the initial state to variants.hidden&lt;/span&gt;
    &lt;span class="na"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"enter"&lt;/span&gt; &lt;span class="c1"&gt;// Animated state to variants.enter&lt;/span&gt;
    &lt;span class="na"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"exit"&lt;/span&gt; &lt;span class="c1"&gt;// Exit state (used later) to variants.exit&lt;/span&gt;
    &lt;span class="na"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;linear&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Set the transition to linear&lt;/span&gt;
    &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;motion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nt"&gt;main&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;The &lt;code&gt;initial&lt;/code&gt; and &lt;code&gt;animate&lt;/code&gt; states will control the entry animation for this component. When you change the page on my website, you should see the content change from having an opacity of &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;x&lt;/code&gt; position of &lt;code&gt;-200px&lt;/code&gt; to having an opacity of &lt;code&gt;1&lt;/code&gt; and being in the center of the screen. This gives the effect of the content fading in from the left. By the way, "A Transition is an object that defines how values animate from one state to another" - &lt;a href="https://www.framer.com/api/motion/types/#transition" rel="noopener noreferrer"&gt;from the Framer Motion website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An entry animation is great but let's go a little further and animate components when they leave the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding &lt;a href="https://www.framer.com/api/motion/animate-presence/" rel="noopener noreferrer"&gt;&lt;code&gt;AnimatePresence&lt;/code&gt;&lt;/a&gt; and exit animations
&lt;/h3&gt;

&lt;p&gt;One feature of Framer Motion is that it can animate components after they've left the React DOM. To activate this feature you can use the &lt;a href="https://www.framer.com/api/motion/animate-presence/" rel="noopener noreferrer"&gt;&lt;code&gt;AnimatePresence&lt;/code&gt;&lt;/a&gt; component. For my website, I use the optional &lt;code&gt;exitBeforeEnter&lt;/code&gt; prop which tells the entrance animation to wait until the exit animation has ended before starting - without this the content would mount on top of the unmounting content, looking messy.&lt;/p&gt;

&lt;p&gt;You'll need to add the &lt;code&gt;AnimatePresence&lt;/code&gt; component to the &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/pages/_app.tsx#L58" rel="noopener noreferrer"&gt;&lt;code&gt;_app.js&lt;/code&gt; file&lt;/a&gt; so that it never unmounts (unmounting would disable the exit animations). Note also the &lt;code&gt;initial={false}&lt;/code&gt; prop which disables the entry animation when you first visit the website. Disabling it is just a personal preference, remove that line if you want to enable it.&lt;/p&gt;

&lt;p&gt;Once &lt;code&gt;AnimatePresence&lt;/code&gt; is added to &lt;code&gt;_app.js&lt;/code&gt;, you can add an &lt;code&gt;exit&lt;/code&gt; animation to your &lt;code&gt;motion.main&lt;/code&gt; component. See this in the two code blocks above.&lt;/p&gt;

&lt;p&gt;We're almost finished but we just need to fix an issue with Next.js scrolling to the top of the page when the route changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solving the scroll on link change issue
&lt;/h3&gt;

&lt;p&gt;When adding page navigation to a Next.js application you should be using the &lt;a href="https://nextjs.org/docs/api-reference/next/link" rel="noopener noreferrer"&gt;&lt;code&gt;Link&lt;/code&gt;&lt;/a&gt; component. By default, when the &lt;code&gt;Link&lt;/code&gt; component is clicked it scrolls to the top of the page before animating, making the page transitions look a bit clunky. See below:&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%2F1xxv0ezfw8k6xxqq3k4a.gif" 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%2F1xxv0ezfw8k6xxqq3k4a.gif"&gt;&lt;/a&gt;&lt;br&gt;Scrolling to the top before animating
  &lt;/p&gt;

&lt;p&gt;Fortunately the fix for this is pretty easy. For each &lt;code&gt;Link&lt;/code&gt; component that is used around your codebase, add the &lt;a href="https://nextjs.org/docs/api-reference/next/link#disable-scrolling-to-the-top-of-the-page" rel="noopener noreferrer"&gt;&lt;code&gt;scroll={false}&lt;/code&gt;&lt;/a&gt; prop. This will disable the scrolling when its clicked. To make this easier and maintain clean code, I created a component that wraps &lt;code&gt;Link&lt;/code&gt; but disables the scroll. I called it &lt;code&gt;NoScrollLink&lt;/code&gt; and you can &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/components/NoScrollLink.tsx" rel="noopener noreferrer"&gt;view it on GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;After disabling the &lt;code&gt;Link&lt;/code&gt; component's scroll, it's a good idea to scroll to the top of the page after the Framer Motion exit animation has completed. This gives the effect of content leaving at the current scroll height but the new content entering at the top of the page. Again this is easy, you can use the &lt;code&gt;onExitComplete&lt;/code&gt; prop on the &lt;code&gt;AnimatePresence&lt;/code&gt; component in &lt;code&gt;_app.js&lt;/code&gt;. The following code snippet will scroll to the top once the exit animation has completed.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="nx"&gt;onExitComplete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scrollTo&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/pages/_app.tsx#L61" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having added that, when you change page Framer Motion should unmount the old content, scroll to the top and mount the new content.&lt;/p&gt;




&lt;h3&gt;
  
  
  The finished product
&lt;/h3&gt;

&lt;p&gt;If you've been following along or want to see it live &lt;a href="http://wallis.dev" rel="noopener noreferrer"&gt;on my website&lt;/a&gt; you'll see the following page transitions:&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%2Fgctmesek0r4ibpopdmeq.gif" 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%2Fgctmesek0r4ibpopdmeq.gif"&gt;&lt;/a&gt;&lt;br&gt;The finished page animation
  &lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article I wanted to help others add page transitions to their Next.js app with the help of Framer Motion. I overcame some obstacles when adding them to my website such as realising &lt;code&gt;AnimatePresence&lt;/code&gt; needed to be in &lt;code&gt;_app.js&lt;/code&gt; and how to stop the scroll to the top of the page after a &lt;code&gt;Link&lt;/code&gt; is clicked.&lt;/p&gt;

&lt;p&gt;If you've anything to add or just want to show some appreciation, leave a comment or react! &lt;/p&gt;

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

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I made a free Dev.to Writing Streak Calculator using Next.js, Day.js and the Dev.to API</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Tue, 13 Apr 2021 22:26:44 +0000</pubDate>
      <link>https://dev.to/jameswallis/i-made-a-free-dev-to-writing-streak-calculator-that-anyone-can-use-4m07</link>
      <guid>https://dev.to/jameswallis/i-made-a-free-dev-to-writing-streak-calculator-that-anyone-can-use-4m07</guid>
      <description>&lt;p&gt;Do you wonder what your current weekly posting streak is?&lt;/p&gt;

&lt;p&gt;On Monday (12th April 2021), I was awarded the 16-week streak badge. It was a surprise, albeit a welcome one, as I figured that I was two weeks away from writing for sixteen continuous weeks with my first article being published &lt;a href="https://dev.to/jameswallis/deploying-a-next-js-project-on-vercel-in-less-than-three-minutes-with-a-custom-domain-568o"&gt;on January 10th, 2021&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This made me think - how does Dev.to calculate the writing streak? Can I build a tool to help fellow Dev.to community members understand if they're on track for their next milestone.&lt;/p&gt;

&lt;p&gt;Below you'll find the answers - tl;dr made a tool that's available for anyone to use.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://devto-writing-streak-calculator.wallis.dev" rel="noopener noreferrer"&gt;The Dev.to Writing Streak Calculator&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I'll demo the app first and further down explain how I calculate the user's current writing streak.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5r9gco37i55t394d6geg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5r9gco37i55t394d6geg.png" width="800" height="488"&gt;&lt;/a&gt;&lt;br&gt;The Writing Streak Calculator
  &lt;/p&gt;

&lt;p&gt;The app is simple by design. &lt;/p&gt;

&lt;p&gt;It consists of a single input box for your username and a text box next to it. Once you've entered your username it will calculate your current writing streak and report it back to you (with some text that is &lt;a href="https://github.com/forem/forem/blob/master/app/services/badges/award_streak.rb#L3-L12" rel="noopener noreferrer"&gt;&lt;em&gt;borrowed&lt;/em&gt; from the Forem codebase&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;To build it I used my usual tools with a couple of others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.forem.com/api" rel="noopener noreferrer"&gt;Dev.to API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://day.js.org" rel="noopener noreferrer"&gt;DayJS&lt;/a&gt; - Heavily for calculating the streaks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://swr.vercel.app" rel="noopener noreferrer"&gt;SWR&lt;/a&gt; - Fetches articles and user data from the Dev.to API&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://preactjs.com" rel="noopener noreferrer"&gt;Preact&lt;/a&gt; - Reduced first load size from 83kB to 49kB (Used for the first time, seems good!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/james-wallis/devto-writing-streak-calculator" rel="noopener noreferrer"&gt;Source code on GitHub.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is pretty much it. At this point, there are no overly complicated features. Some things I'd like to add, however, are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Date since last posted&lt;/li&gt;
&lt;li&gt;Longest writing streak&lt;/li&gt;
&lt;li&gt;A warning that the user needs to post if they're close to the end-of-week deadline&lt;/li&gt;
&lt;li&gt;Support other Forem sites (CodeNewbie etc)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Something more you want to see? Add it in the comments or &lt;a href="https://github.com/james-wallis/devto-writing-streak-calculator/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In case you missed it above, here's the link for the tool. Go check out your writing streak!&lt;br&gt;
&lt;a href="https://devto-writing-streak-calculator.wallis.dev" rel="noopener noreferrer"&gt;https://devto-writing-streak-calculator.wallis.dev&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus - display on your own site
&lt;/h3&gt;

&lt;p&gt;There is also an API route that will fetch your latest and longest writing streak. You just pass your &lt;code&gt;username&lt;/code&gt; as a query param. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://devto-writing-streak-calculator.wallis.dev/api/calculate?username=jameswallis" rel="noopener noreferrer"&gt;https://devto-writing-streak-calculator.wallis.dev/api/calculate?username=jameswallis&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I use it to display my current writing streak on my &lt;a href="https://dev.to/jameswallis/i-built-a-dev-to-analytics-dashboard-to-track-historic-post-data-2c7o"&gt;Advanced Dev.to Dashboard with historical analytics&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1pa3rq439tco4vlo2jx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1pa3rq439tco4vlo2jx.png" width="800" height="115"&gt;&lt;/a&gt;&lt;br&gt;Writing Streak displayed on my analytics dashboard (final box on the right)
  &lt;/p&gt;




&lt;h2&gt;
  
  
  How the writing streak is calculated
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prewarning&lt;/strong&gt;: There is the possibility that I'm calculating it incorrectly. Hopefully, a Forem contributor will call me out and I can correct it! Otherwise, if you think your streak isn't right let me know in the comments or &lt;a href="https://github.com/james-wallis/devto-writing-streak-calculator/issues" rel="noopener noreferrer"&gt;raise an issue on GitHub&lt;/a&gt;. That being said, it's correct for me for two separate streaks so I'm confident.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I started by noting when I was awarded the 16 weeks and 4-week badges (I deleted the email for 8 weeks):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4 weeks: Monday 17th August 2020&lt;/li&gt;
&lt;li&gt;16 weeks: Monday 12th April 2021&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So badges are awarded on Mondays.&lt;/p&gt;

&lt;p&gt;Next, I wanted to understand why my streak wasn't broken between the 30th December 2020 (2nd post in the streak) and the 10th January 2021 (3rd post in the streak) - that's an 11 day difference. After searching for missing articles and trying to find reason in the Dev.to Ruby on Rails code, I eventually just checked a calendar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmuv2yfolf1icalmtmbky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmuv2yfolf1icalmtmbky.png" width="513" height="270"&gt;&lt;/a&gt;&lt;br&gt;December 2020 - January 2021. Red circles denote published dates.
  &lt;/p&gt;

&lt;p&gt;It turned out that the 10th January 2021 was the week after the 30th December 2020 when the week starts on a Monday.&lt;/p&gt;

&lt;p&gt;So I came to the conclusion that as long as a post is published between Monday and Sunday then the streak would continue!&lt;/p&gt;

&lt;p&gt;Here's the link again:&lt;br&gt;
&lt;a href="https://devto-writing-streak-calculator.wallis.dev" rel="noopener noreferrer"&gt;https://devto-writing-streak-calculator.wallis.dev&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I've introduced the writing streak calculator tool made for the Dev.to community.&lt;/p&gt;

&lt;p&gt;If you've enjoyed this article or the tool, react! &lt;/p&gt;

&lt;p&gt;Let me know your latest streak or any comments down below.&lt;/p&gt;

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

</description>
      <category>productivity</category>
      <category>showdev</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>I built an advanced Dev.to dashboard with historic data using Next.js and Azure Functions 📈</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Tue, 06 Apr 2021 22:55:59 +0000</pubDate>
      <link>https://dev.to/jameswallis/i-built-a-dev-to-analytics-dashboard-to-track-historic-post-data-2c7o</link>
      <guid>https://dev.to/jameswallis/i-built-a-dev-to-analytics-dashboard-to-track-historic-post-data-2c7o</guid>
      <description>&lt;p&gt;Do you ever watch your view count rise and wonder which posts are being read the most? I know I did. &lt;/p&gt;

&lt;p&gt;That is one reason why I chose to develop my own Dev.to analytics dashboard to display historical data such as view, reaction and follower increase over the last 24 hours, 7 days and 30 days.&lt;/p&gt;

&lt;p&gt;You can view the live dashboard here: &lt;a href="https://devto-analytics.wallis.dev" rel="noopener noreferrer"&gt;https://devto-analytics.wallis.dev&lt;/a&gt;. &lt;em&gt;You'll notice that it's styled to look like Dev.to.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Incredibly, Dev.to has recently added &lt;a href="https://dev.to/dashboard/analytics"&gt;their own analytics page&lt;/a&gt; as well as having an "analytics" option on each post (next to edit, manage). There are some differences between the built-in dashboard and mine.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why build an analytics dashboard?
&lt;/h2&gt;

&lt;p&gt;I really enjoy posting on Dev.to. I &lt;a href="https://dev.to/jameswallis/i-completely-rewrote-my-personal-website-using-dev-to-as-a-cms-2pje"&gt;recently rewrote my whole website to use Dev.to as a CMS&lt;/a&gt; to enable myself to continue posting here while using my own site as the canonical URL.&lt;/p&gt;

&lt;p&gt;I wanted, however, to see more in-depth information about each post such as view, reaction and follower increases over a given period of time. By default, I didn't have access to any data that would allow me to calculate any increases.&lt;/p&gt;

&lt;p&gt;As a result, I ended up building my own analytics dashboard using Next.js, Tailwind CSS, Recharts, the Dev.to API and Azure Functions &amp;amp; Cosmos DB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/james-wallis/devto-analytics" rel="noopener noreferrer"&gt;You can view the code on GitHub.&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How I source the historical data
&lt;/h2&gt;

&lt;p&gt;If you've used the Dev.to API before, you'll have noticed that, for the moment, you're unable to access any historical data. This makes it difficult to know how popular a post is over a given period of time (before the built-in analytics).&lt;/p&gt;

&lt;p&gt;To record my historical data I created an &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/" rel="noopener noreferrer"&gt;Azure Function&lt;/a&gt; that saves my latest &lt;a href="https://docs.forem.com/api/#operation/getUserAllArticles" rel="noopener noreferrer"&gt;article&lt;/a&gt; and &lt;a href="https://docs.forem.com/api/#operation/getFollowers" rel="noopener noreferrer"&gt;follower&lt;/a&gt; data, gathered using the Dev.to API, to a &lt;a href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction" rel="noopener noreferrer"&gt;Azure Cosmos Database&lt;/a&gt;. The functions that save my article and follower data run every hour.&lt;/p&gt;

&lt;p&gt;In addition, I created a couple of HTTP Azure Functions so that I can access the data.&lt;/p&gt;

&lt;p&gt;If you're wondering, I'm entirely within the free tier on Azure - so it isn't costing me anything to record my historic data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/james-wallis/devto-analytics/tree/main/azure-functions" rel="noopener noreferrer"&gt;You can view the Azure Functions implementation on GitHub.&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
   The dashboard
&lt;/h2&gt;

&lt;p&gt;The dashboard consists of three pages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Home/overview page&lt;/strong&gt; - basically an overview of my current stats and periodical view/reaction/comment/follower increases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Breakdown graphs page&lt;/strong&gt; - Charts that break down stats from the overview page so that I can easily see information such as what articles have been most read this week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summary graphs page&lt;/strong&gt; - Charts that display the increase of views/reactions/followers from hour-to-hour and day-to-day&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/james-wallis/devto-analytics/tree/main/dashboard" rel="noopener noreferrer"&gt;You can view the dashboard implementation on GitHub.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://devto-analytics.wallis.dev/" rel="noopener noreferrer"&gt;Home/overview page&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The dashboard's UI is based heavily on Dev.to's styling. I wanted it to appear as an extension to Dev.to's current implementation. The home page is styled to look like an advanced version of the &lt;a href="https://dev.to/dashboard"&gt;Dev.to dashboard&lt;/a&gt; page that contains your general stat overview (total page views, reactions, comments) and a list of any draft and published articles. &lt;/p&gt;

&lt;p&gt;Moreover, it was a lot of fun essentially cloning Dev.to to try to make the dashboard look as alike as possible. Try switching between the dashboard home page and your Dev.to dashboard in different tabs!&lt;/p&gt;

&lt;p&gt;On my dashboard, I've added follower and last posted date to the overview stats - as I care about them more than "Listing's created" and "Credits available". Most overview stats also contain two pieces of historical data. For page views, for example, I'm displaying the total page view increase for the past 24 hours and the past 7 days.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvh0zb8s15smg9dbnt8w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvh0zb8s15smg9dbnt8w.png" width="800" height="153"&gt;&lt;/a&gt;&lt;br&gt;Overview stats
  &lt;/p&gt;

&lt;p&gt;Looking further down the page, you'll see a sortable list of my published posts containing the usual stats for each (views, reactions and comments). I've also added the 24 hours, 7 days and 30 day increase for each stat, for every post.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ijg08ez02uey0j31c2e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ijg08ez02uey0j31c2e.png" width="800" height="233"&gt;&lt;/a&gt;&lt;br&gt;Individual post stats
  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://devto-analytics.wallis.dev/graphs/breakdown" rel="noopener noreferrer"&gt;Breakdown graphs page&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This page displays graphs that break down increases for views, reactions and comments. Its purpose is to show me what posts have been most popular over a given period of time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlu3ch7aer2clvh6i3fs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlu3ch7aer2clvh6i3fs.png" width="800" height="314"&gt;&lt;/a&gt;&lt;br&gt;Hovering over a breakdown graph
  &lt;/p&gt;

&lt;p&gt;While its styling is based on Dev.to, unlike the home page I haven't copied a specific Dev.to page.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://devto-analytics.wallis.dev/graphs/summary" rel="noopener noreferrer"&gt;Summary graphs page&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The final page contains graphs that details how views, reactions and comments have increased over a period of time. Using this page I can interpret such information as if my follower increases have stagnated or are increasing at a good level.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4410vfltycevkwu7uqq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4410vfltycevkwu7uqq.png" width="800" height="314"&gt;&lt;/a&gt;&lt;br&gt;Page view trends over the past 24 hours and 7 days
  &lt;/p&gt;

&lt;h3&gt;
  
  
  Difference between my dashboard and the built-in analytics page
&lt;/h3&gt;

&lt;p&gt;If you've read this far you're likely wondering what the difference between my dashboard and the built-in Dev.to analytics dashboard. The following is my opinion, let me know in the comments if you disagree.&lt;/p&gt;

&lt;p&gt;What Dev.to built-in analytics does better:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Dev.to has access to a lot more historical data so they can report your page views way back&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm bias, let me know if you think there are more than this.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What my dashboard does better:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Integrates increases/historical data into the main dashboard screen better. I'd love it if my &lt;a href="https://dev.to/dashboard"&gt;Dev.to dashboard&lt;/a&gt; displayed my daily, weekly and monthly increases for each post.&lt;/li&gt;
&lt;li&gt;Displays follower and last posted date in the overview stats - I don't care about listings or credits. Moreover, I'd love to display my current posting streak instead of the time since I last posted. This would help me ensure I'm on track for my 16-week badge.&lt;/li&gt;
&lt;li&gt;The breakdown page makes it easy to see why my view count has risen.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Technical details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; - powers the dashboard.

&lt;ul&gt;
&lt;li&gt;The built-in data fetching method &lt;a href="https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation" rel="noopener noreferrer"&gt;&lt;code&gt;getStaticProps&lt;/code&gt;&lt;/a&gt; is used to preload the article and follower data at build time. &lt;a href="https://nextjs.org/docs/basic-features/data-fetching#incremental-static-regeneration" rel="noopener noreferrer"&gt;Incremental Static Regeneration&lt;/a&gt; rebuilds the page to minimise how out of date the initially served page is.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://swr.vercel.app/" rel="noopener noreferrer"&gt;&lt;code&gt;useSWR&lt;/code&gt;&lt;/a&gt; fetches the current article and follower data once the page has loaded.&lt;/li&gt;
&lt;li&gt;Combining &lt;code&gt;getStaticProps&lt;/code&gt; and &lt;code&gt;useSWR&lt;/code&gt; means that the dashboard loads fast, but will always display the most up-to-date data.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction" rel="noopener noreferrer"&gt;Azure Cosmos DB&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Saves historical data gathered using the Dev.to API.&lt;/li&gt;
&lt;li&gt;Serves the data via a HTTP route for the UI to consume.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt; - styling&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://react-icons.github.io/react-icons/" rel="noopener noreferrer"&gt;React-icons&lt;/a&gt; - various icons such as the question mark and GitHub icon on the navigation bar. &lt;/li&gt;

&lt;li&gt;

&lt;a href="https://recharts.org/" rel="noopener noreferrer"&gt;Recharts&lt;/a&gt; - the chart library used on the graph pages&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://www.typescriptlang.org/docs/handbook/project-references.html" rel="noopener noreferrer"&gt;TypeScript project references&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Enable the sharing of TypeScript interfaces, and other code, between the dashboard and the Azure Functions codebases.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/james-wallis/devto-analytics/tree/main/common" rel="noopener noreferrer"&gt;View in the &lt;code&gt;common&lt;/code&gt; directory of the GitHub repository.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For more information on TypeScript project references, &lt;a href="https://dev.to/jameswallis/using-typescript-project-references-to-share-common-code-p8o"&gt;read my post on them&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Future improvements
&lt;/h2&gt;

&lt;p&gt;There are few features that I want to add to the dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calculate and display my posting streak (by week) in the stat overview - should help with the 16-week streak badge.&lt;/li&gt;
&lt;li&gt;Average stats - such as average page views or followers gained per day.&lt;/li&gt;
&lt;li&gt;Predicted stats - days until I hit 100,000 page views for example.&lt;/li&gt;
&lt;li&gt;Display whether I've added a canonical URL to the post (for it to display on &lt;a href="https://wallis.dev/blog/I-built-a-devto-analytics-dashboard" rel="noopener noreferrer"&gt;my website&lt;/a&gt;) and reposted it on other sites such as &lt;a href="https://hashnode.wallis.dev/i-built-a-devto-analytics-dashboard-to-track-historic-post-data" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt; and &lt;a href="https://james-wallis.medium.com/i-built-a-dev-to-analytics-dashboard-to-track-historic-post-data-7f4032c66c2c" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post, I've introduced &lt;a href="https://devto-analytics.wallis.dev" rel="noopener noreferrer"&gt;my Dev.to analytics dashboard&lt;/a&gt; that helps me understand how my stats are changing over time and identify popular posts.&lt;/p&gt;

&lt;p&gt;Would you like to make your own dashboard like this? Have any thoughts on the dashboard? Let me know in the comments!&lt;/p&gt;

&lt;p&gt;If you are looking to fork/clone my &lt;a href="https://github.com/james-wallis/devto-analytics" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; then I'll need to make the documentation a little better 😅 - it's pretty straightforward to do once your Azure Functions/Cosmos DB is set up. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. Why isn't it password protected?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm aware that by posting this blog I'm allowing access to my private post data to anyone who stumbles across it. I'm OK with this because: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I want to show the dashboard off as a portfolio piece&lt;/li&gt;
&lt;li&gt;I want others to be able to copy/clone it so that they can see their own historical Dev.to data&lt;/li&gt;
&lt;li&gt;I couldn't come up with a reason to hide it. I understand why it is private by default but in my case, I'm happy with others seeing it.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Let me know what you think of &lt;a href="https://devto-analytics.wallis.dev/" rel="noopener noreferrer"&gt;my Dev.to analytics dashboard&lt;/a&gt;!&lt;/p&gt;

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

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Using TypeScript Project References to share common code</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Tue, 30 Mar 2021 22:50:43 +0000</pubDate>
      <link>https://dev.to/jameswallis/using-typescript-project-references-to-share-common-code-p8o</link>
      <guid>https://dev.to/jameswallis/using-typescript-project-references-to-share-common-code-p8o</guid>
      <description>&lt;p&gt;Ever wondered if you can share interfaces, types and functions between TypeScript projects?&lt;/p&gt;

&lt;p&gt;I'm currently developing a project consisting of two separate TypeScript applications, one being a React.js dashboard and the other an Azure Function app written in Node.js. As part of the project, the dashboard calls an API in the Azure Function app. This got me thinking, as I'm in control of both the data source and the application that uses the data, is there a way that I can share certain interfaces between the two projects?&lt;/p&gt;

&lt;p&gt;The answer is yes, since version 3 of TypeScript you can use &lt;a href="https://www.typescriptlang.org/docs/handbook/project-references.html" rel="noopener noreferrer"&gt;Project References&lt;/a&gt; to share code between TypeScript projects. When using Project References in my project, however, I couldn't find any official examples on how to use them - hence this post! &lt;/p&gt;

&lt;p&gt;&lt;em&gt;While the implementation below is what has worked for me, if you have any improvements, let me know in the comments.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
   What are &lt;a href="https://www.typescriptlang.org/docs/handbook/project-references.html" rel="noopener noreferrer"&gt;Project References&lt;/a&gt;?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Project references allow you to structure your TypeScript programs into smaller pieces. By doing this, you can greatly improve build times, enforce logical separation between components, and organize your code in new and better ways.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
   How to use
&lt;/h2&gt;

&lt;p&gt;Take a project that consists of a frontend and a backend written in TypeScript. Both contain an interface called &lt;code&gt;IData&lt;/code&gt; which is exactly the same. Currently, each time I make a change, I have to duplicate it in the other file (which is extremely annoying). &lt;/p&gt;

&lt;p&gt;The directory of the project is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myproject
- frontend
  - app.ts
  - interfaces
    - IData.ts
  - tsconfig.json
- backend
  - server.ts
  - interfaces
    - IData.ts
  - tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to use a single &lt;code&gt;IData.ts&lt;/code&gt; file between both projects, we can use Project References.&lt;/p&gt;

&lt;h3&gt;
  
  
   Adding the common TypeScript project
&lt;/h3&gt;

&lt;p&gt;We will start by creating a third TypeScript project called &lt;code&gt;common&lt;/code&gt;, adding an empty &lt;code&gt;tsconfig.json&lt;/code&gt; file and copying the &lt;code&gt;IData.ts&lt;/code&gt; interface over. We can also remove it from the &lt;code&gt;frontend&lt;/code&gt; and &lt;code&gt;backend&lt;/code&gt; apps. So the directory structure will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myproject
- frontend
  - app.ts
  - tsconfig.json
- backend
  - server.ts
  - tsconfig.json
- common
  - interfaces
    - IData.ts
  - tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't enough though. In the &lt;code&gt;common&lt;/code&gt; app's &lt;code&gt;tsconfig.json&lt;/code&gt; we need to add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;whatever&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;want&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es2015"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;whatever&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;want&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"declaration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"declarationMap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"composite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key parts are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;declaration&lt;/code&gt;: Generates a declaration file that the &lt;code&gt;frontend&lt;/code&gt; and &lt;code&gt;backend&lt;/code&gt; apps can use to reference items in the &lt;code&gt;common&lt;/code&gt; app.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;composite&lt;/code&gt;: Ensures TypeScript can quickly determine where to find the outputs of the referenced project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;declarationMap&lt;/code&gt;: Enables editor features like “Go to Definition” and Rename to transparently navigate and edit code across project boundaries in supported editors&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Referencing the common project in &lt;code&gt;frontend&lt;/code&gt;/&lt;code&gt;backend&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;To reference the common &lt;code&gt;IData&lt;/code&gt; interface in the &lt;code&gt;frontend&lt;/code&gt; and &lt;code&gt;backend&lt;/code&gt; apps we need to make a simple change to both of their &lt;code&gt;tsconfig.json&lt;/code&gt; files. Add the &lt;code&gt;references&lt;/code&gt; property to your existing &lt;code&gt;tsconfig.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;usual&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"references"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../common"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
   Building the &lt;code&gt;frontend&lt;/code&gt;/&lt;code&gt;backend&lt;/code&gt; apps
&lt;/h3&gt;

&lt;p&gt;Now that we've added the reference to the common app in order to access its interfaces we need to compile both the &lt;code&gt;frontend&lt;/code&gt; and &lt;code&gt;backend&lt;/code&gt; apps. &lt;/p&gt;

&lt;p&gt;When doing so, ensure you use the &lt;code&gt;--build&lt;/code&gt; option so that TypeScript automatically builds all referenced projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tsc --build .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: If you're using Next.js with TypeScript, I didn't need to do this. Both &lt;code&gt;next dev&lt;/code&gt; and &lt;code&gt;next build&lt;/code&gt; kept working just the same.&lt;/p&gt;

&lt;h3&gt;
  
  
   Importing the common interface into &lt;code&gt;frontend&lt;/code&gt;/&lt;code&gt;backend&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is easier than you might first think, just import &lt;code&gt;IData&lt;/code&gt; using its relative path. TypeScript will do the magic when you compile it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;IData&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../common/interfaces/IData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: if your compiler has problems with the &lt;code&gt;IData&lt;/code&gt; file, you can add &lt;code&gt;type&lt;/code&gt; after the &lt;code&gt;import&lt;/code&gt;. See: &lt;a href="https://dev.to/uppajung/comment/1f6bc"&gt;https://dev.to/uppajung/comment/1f6bc&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post, I've demonstrated how to use TypeScript &lt;a href="https://www.typescriptlang.org/docs/handbook/project-references.html" rel="noopener noreferrer"&gt;Project References&lt;/a&gt; to use a common project for shared interfaces, functions, types and more!&lt;/p&gt;

&lt;p&gt;Feedback on my approach is appreciated! As I said above, I couldn't find an official example to guide me on how to use Project References so any feedback in the comments will help me improve this tutorial and my own TypeScript projects!&lt;/p&gt;

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

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Using getStaticProps and getStaticPaths with TypeScript - Next.js</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Wed, 24 Mar 2021 21:24:10 +0000</pubDate>
      <link>https://dev.to/jameswallis/using-getstaticprops-and-getstaticpaths-with-typescript-next-js-2d7e</link>
      <guid>https://dev.to/jameswallis/using-getstaticprops-and-getstaticpaths-with-typescript-next-js-2d7e</guid>
      <description>&lt;p&gt;My &lt;a href="https://wallis.dev" rel="noopener noreferrer"&gt;personal website&lt;/a&gt; is &lt;a href="https://dev.to/jameswallis/i-completely-rewrote-my-personal-website-using-dev-to-as-a-cms-2pje"&gt;built on Next.js&lt;/a&gt; and uses both the &lt;code&gt;getStaticProps&lt;/code&gt; and &lt;code&gt;getStaticPaths&lt;/code&gt; functions to dynamically generate the &lt;code&gt;/blog/&lt;/code&gt; and &lt;code&gt;/portfolio/&lt;/code&gt; pages at build time. While updating both methods to use their proper TypeScript types, &lt;a href="https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops" rel="noopener noreferrer"&gt;following the documentation&lt;/a&gt;, I ran into an error when reading the parameter that I was passing from &lt;code&gt;getStaticPaths&lt;/code&gt; into &lt;code&gt;getStaticProps&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The error that appeared was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Property 'slug' does not exist on type 'ParsedUrlQuery | undefined'&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After doing some research and finding &lt;a href="https://github.com/vercel/next.js/discussions/16522" rel="noopener noreferrer"&gt;a discussion on the Next.js GitHub regarding this issue&lt;/a&gt;, I recognised it was a gap in their documentation. It explains how to add the type to &lt;code&gt;getStaticProps&lt;/code&gt; when used on its own but it doesn't demonstrate how to access the property you've declared in &lt;code&gt;getStaticPaths&lt;/code&gt;.&lt;/p&gt;




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

&lt;p&gt;&lt;a href="https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation" rel="noopener noreferrer"&gt;&lt;code&gt;getStaticProps&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation" rel="noopener noreferrer"&gt;&lt;code&gt;getStaticPaths&lt;/code&gt;&lt;/a&gt; are two methods that can be used for data fetching in Next.js. Briefly speaking &lt;code&gt;getStaticProps&lt;/code&gt; lets you fetch data at build time and &lt;code&gt;getStaticPaths&lt;/code&gt; enables you to specify dynamic routes to pre-render pages based on data.&lt;/p&gt;

&lt;p&gt;For more information on these functions, read my post on &lt;a href="https://dev.to/jameswallis/different-ways-to-fetch-data-in-next-js-server-side-and-when-to-use-them-1jb0"&gt;different ways to fetch data in Next.js&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The error
&lt;/h2&gt;

&lt;p&gt;Using the example code below I will demonstrate the TypeScript error and advise you on how to fix it. I'm using the variable name &lt;code&gt;slug&lt;/code&gt; to create the dynamic routes, but you could use anything - another common name is &lt;code&gt;id&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GetStaticPaths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GetStaticProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ParsedUrlQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;querystring&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStaticPaths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GetStaticPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arr&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;slug&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="k"&gt;return&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="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="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GetStaticProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This is where the error occurs&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;slug&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="c1"&gt;// Property 'slug' does not exist on type 'ParsedUrlQuery | undefined'&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/&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;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the first line of the &lt;code&gt;getStaticProps&lt;/code&gt;. Here we are attempting to access the slug variable that was created in &lt;code&gt;getStaticPaths&lt;/code&gt; and returned inside the &lt;code&gt;params&lt;/code&gt; object. This is the line that causes the error as &lt;code&gt;context.params&lt;/code&gt; has the type &lt;code&gt;ParsedUrlQuery | undefined&lt;/code&gt; and &lt;code&gt;slug&lt;/code&gt; does not exist in &lt;code&gt;ParsedUrlQuery&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Fortunately, fixing the issue is as simple as creating an interface that extends &lt;code&gt;ParsedUrlQuery&lt;/code&gt; and contains a &lt;code&gt;slug&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IParams&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ParsedUrlQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we've added that to the file, we need to assert that &lt;code&gt;context.params&lt;/code&gt; is of type &lt;code&gt;IParams&lt;/code&gt;. This is done like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IParams&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's as simple as that! Just adding the &lt;code&gt;IParams&lt;/code&gt; interface makes the TypeScript error disappear.&lt;/p&gt;

&lt;p&gt;Updated example code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GetStaticPaths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GetStaticProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ParsedUrlQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;querystring&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IParams&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ParsedUrlQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStaticPaths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GetStaticPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arr&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;slug&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="k"&gt;return&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="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="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GetStaticProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IParams&lt;/span&gt; &lt;span class="c1"&gt;// no longer causes error&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/&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;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Round up
&lt;/h2&gt;

&lt;p&gt;If this post has helped you use Next.js together with TypeScript, react or let me know in the comments! &lt;/p&gt;

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

&lt;p&gt;Sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/basic-features/data-fetching" rel="noopener noreferrer"&gt;Data fetching in Next.js (&lt;code&gt;getStaticProps&lt;/code&gt; and &lt;code&gt;getStaticPaths&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vercel/next.js/discussions/16522" rel="noopener noreferrer"&gt;GitHub discussion on the type for &lt;code&gt;context.params.slug&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The easiest way to download SVGs from a website</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Tue, 23 Mar 2021 01:37:09 +0000</pubDate>
      <link>https://dev.to/jameswallis/the-easiest-way-to-download-svgs-from-a-website-3og9</link>
      <guid>https://dev.to/jameswallis/the-easiest-way-to-download-svgs-from-a-website-3og9</guid>
      <description>&lt;p&gt;Usually, when adding icons to a website, I'll use &lt;a href="https://react-icons.github.io/react-icons/" rel="noopener noreferrer"&gt;React-icons&lt;/a&gt;. There are times, however, when I prefer to copy an exact SVG from a website. Downloading an SVG isn't as easy as downloading an image on a website. Fortunately, &lt;a href="https://svgexport.io/" rel="noopener noreferrer"&gt;SVG Export&lt;/a&gt; extracted them for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Ever use inspect element to download images that you're unable to right-click and either save or open? You'll likely have noticed that with SVGs it's not easy to download them from a website. - or at least I did when I was attempting to "clone" a webpage for my latest project.&lt;/p&gt;

&lt;p&gt;Take the Dev.to logo that is visible on every page on Dev.to, for example. Right-clicking on that doesn't give you the option to open it in a new tab or save it. Moreover, using inspect element only gives you the potentially huge SVG tags, leaving you to work out where the SVG begins and ends in the page's HTML.&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%2Fg6h4aowgsjcg73hzounl.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%2Fg6h4aowgsjcg73hzounl.png"&gt;&lt;/a&gt;&lt;br&gt;Unable to open or download the Dev.to icon SVG!
  &lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;Enter &lt;a href="https://svgexport.io/" rel="noopener noreferrer"&gt;SVG Export&lt;/a&gt;, by &lt;a href="https://twitter.com/delanebob" rel="noopener noreferrer"&gt;Stephen Delaney&lt;/a&gt; is a browser extension &lt;a href="https://chrome.google.com/webstore/detail/svg-export/naeaaedieihlkmdajjefioajbbdbdjgp/related?hl=en-GB" rel="noopener noreferrer"&gt;for Google Chrome&lt;/a&gt; and &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/svg-export" rel="noopener noreferrer"&gt;Mozilla Firefox&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;After installing the extension, clicking the extension icon will trigger SVG Export to extract all available SVGs on your current webpage, ensuring their inline styles are kept. A new tab will be opened displaying all SVGs. From there, locate the SVGs you're after and either download the SVG as a file, copy it as code or paste it into your favourite design tools like Sketch, Figma or Framer.&lt;/p&gt;

&lt;p&gt;To download the Dev.to icon mentioned above, I simply opened any page on &lt;a href="https://dev.to"&gt;Dev.to&lt;/a&gt;, clicked the SVG Export icon to start the extract, and finally located it in the newly opened tab.&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%2Fh3lzqjvt09is8niudrsz.gif" 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%2Fh3lzqjvt09is8niudrsz.gif"&gt;&lt;/a&gt;&lt;br&gt;SVG Export in action
  &lt;/p&gt;

&lt;h2&gt;
  
  
  Round up
&lt;/h2&gt;

&lt;p&gt;In this post, I demonstrated how I was able to easily extract an SVG from a webpage using &lt;a href="https://svgexport.io/" rel="noopener noreferrer"&gt;SVG Export&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Do you have another, perhaps better, method of downloading SVGs from websites? If you do, let me know in the comments 👇&lt;/p&gt;

&lt;p&gt;If this article has helped, drop a reaction!&lt;/p&gt;

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

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>50,000 views milestone, continuing my blogging journey</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Tue, 16 Mar 2021 23:59:20 +0000</pubDate>
      <link>https://dev.to/jameswallis/50-000-views-milestone-continuing-my-blogging-journey-4832</link>
      <guid>https://dev.to/jameswallis/50-000-views-milestone-continuing-my-blogging-journey-4832</guid>
      <description>&lt;p&gt;🎉 Last week I hit 50,000 total views! 🎉&lt;/p&gt;

&lt;p&gt;🔥 And also 1000 total reactions! 🔥&lt;/p&gt;

&lt;p&gt;🚀 And was awarded the Next.js badge! 🚀&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm buzzing.&lt;/strong&gt; I wanted to reach 50,000 total views by June this year but it's the start of March and I've already achieved it - I thought for sure that it would be a close call given the time it took to reach 20,000 views. The icing on the cake? &lt;a href="https://dev.to/jameswallis/adding-a-blog-with-a-dev-to-backend-to-a-static-next-js-website-with-canonical-urls-1i9g"&gt;My latest post&lt;/a&gt; earned me the Next.js author of the week badge, which I've been eyeing for ages!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/blSTtZehjAZ8I/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/blSTtZehjAZ8I/giphy.gif" width="250" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back in January, when I reached 20,000 I wrote &lt;a href="https://dev.to/jameswallis/20-000-views-milestone-what-i-ve-learnt-about-blogging-so-far-4laj"&gt;a milestone post&lt;/a&gt; that discussed what I'd learnt about blogging so far. In this post, I'll reflect on it and will attempt to understand &lt;strong&gt;how I've more than doubled my total views in a third of the time&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing &lt;a href="https://dev.to/jameswallis/20-000-views-milestone-what-i-ve-learnt-about-blogging-so-far-4laj"&gt;my 20,000 milestone post&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
   What I'd learnt a few months into posting
&lt;/h3&gt;

&lt;p&gt;In the last post, I created a list of things I'd learnt over the course of my first few months of blogging. Here are some of the points that I still believe are important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Post titles matter.&lt;/strong&gt; I still believe this and carefully word titles trying to include "How to" or some type of call to action to entice the reader.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep posting.&lt;/strong&gt; I've acquired the 8-week posting streak badge and (think) I'm on track for the 16-week badge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't be discouraged by a low amount of reactions.&lt;/strong&gt; While I'm now making posts that gain a considerable amount of attention (for me), I think about half of those that I've posted in the past few months have had ~10 reactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post what you find interesting.&lt;/strong&gt; This is a good one! My most popular posts have been about rebuilding my website to use Dev.to as a CMS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
   What I planned to do next
&lt;/h3&gt;

&lt;p&gt;At the end of the post, I came up with a plan to reach 50,000 views.&lt;/p&gt;

&lt;p&gt;This is what I said...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the next six months, I'm aiming to reach 50,000 views. And, by the end of the year, I want to achieve 100,000 views.&lt;br&gt;
To achieve this I will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create more posts!&lt;/li&gt;
&lt;li&gt;Update old posts with new content and correcting tutorials where the underlying software has changed.&lt;/li&gt;
&lt;li&gt;Publicise my posts across the web and share them on Linkedin and other social sites. So far I haven't advertised them at all and rely on followers on dev.to, it's algorithm and Google rankings to gain views on my articles. &lt;em&gt;This would be beneficial to gain more views, but means advertising my writing to people that know me - so we'll see whether I actually do it or not.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;...and after some reflection, I can tell you that I haven't &lt;em&gt;really&lt;/em&gt; done any of them. I've created more posts and updated a couple of tutorials with corrections. Yet, here I am hitting 50,000 views?&lt;/p&gt;

&lt;h2&gt;
  
  
  So what have I done instead?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Been persistent.&lt;/strong&gt; Currently, I'm creating a post about once a week. This has helped me grow to ~700 followers, that's a lot of people to receive a notification each time I make a post. Additionally, I believe that it's helped me naturally improve the quality of my posts as I gain more experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Categorised my posts.&lt;/strong&gt; Over the past couple of months I've written three types of posts. Sticking to three formats has led to a smooth experience when I write a post as I have a format that I can follow for each type. If I want to, I will deviate from the formats, but they help to get me started.

&lt;ol&gt;
&lt;li&gt;Portfolio items where I write about past projects (these show up on my website under the &lt;code&gt;/portfolio&lt;/code&gt; path, &lt;a href="https://dev.to/jameswallis/i-completely-rewrote-my-personal-website-using-dev-to-as-a-cms-2pje"&gt;more info here&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;"5 things to check out". I've written about &lt;a href="https://dev.to/jameswallis/5-built-in-next-js-features-you-absolutely-should-check-out-4k8e"&gt;5 Next.js features&lt;/a&gt;, &lt;a href="https://dev.to/jameswallis/5-built-in-next-js-features-you-absolutely-should-check-out-4k8e"&gt;5 places to get pre-built Tailwind CSS components&lt;/a&gt;. These posts are fun to write as I have to find 5 things to write about so I'm learning while I'm writing.&lt;/li&gt;
&lt;li&gt;Projects created with a post topic in mind. My two most popular posts have both been about &lt;a href="https://dev.to/jameswallis/i-completely-rewrote-my-personal-website-using-dev-to-as-a-cms-2pje"&gt;rewriting my website to use Dev.to as a CMS&lt;/a&gt;. While this project has made it ten times easier to add posts to my website (with the canonical URL pointing to my site), I knew before starting development that it would be interesting to write about - and that bet has paid off.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Varied my posts.&lt;/strong&gt; Posting every week can be exhausting. Some weeks I'll write a shorter post, like this one, and other weeks I'll set time aside to focus on a longer, tutorial style post.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;🎯 100,000 views 🎯&lt;/p&gt;

&lt;p&gt;Reaching 50,000 views isn't the end of my blogging journey. My next goals are to reach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100,000 total views&lt;/li&gt;
&lt;li&gt;2,000 total reactions&lt;/li&gt;
&lt;li&gt;10,000 views on a single post&lt;/li&gt;
&lt;li&gt;The 16 week streak badge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve this I will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create more posts!&lt;/li&gt;
&lt;li&gt;Cross-post to other blogging sites like &lt;a href="https://medium.com/@james-wallis" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; and &lt;a href="https://hashnode.wallis.dev" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt;. &lt;em&gt;Both seem a lot slower than Dev.to to gain views on posts but I want to persist and add most of my articles there - we'll see how it works out!&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Publicise my posts. I'll start with LinkedIn and then see how I get on. &lt;em&gt;I know I said this last time but I really should start to do this. I've been lucky enough to have been shared by the &lt;a href="https://twitter.com/ThePracticalDev/status/1369800742131884033?s=20" rel="noopener noreferrer"&gt;Dev Community&lt;/a&gt; Twitter account so I really should do the same!&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;I'm curious, how much success do you have on other blogging platforms like Medium and Hashnode? Do you use any others?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I'm continuing to enjoy writing, posting and interacting with members of the Dev.to community. Though, I have to admit I'm excited to achieve the 16-week streak badge so I can have a little break - I wish there was a way to see if am on track.&lt;/p&gt;

&lt;p&gt;Let me know what you think about my blogging experience and if you have any advice for me!&lt;/p&gt;

&lt;p&gt;Where do you cross-post to?&lt;/p&gt;

&lt;p&gt;Thanks for reading! Hope you're having a great 2021!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/WZSCbqzHspAZA7gweg/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/WZSCbqzHspAZA7gweg/giphy.gif" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>yearinreview</category>
      <category>writing</category>
      <category>gratitude</category>
    </item>
    <item>
      <title>How to add a blog using Dev.to as a CMS to a Next.js website</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Wed, 10 Mar 2021 00:00:24 +0000</pubDate>
      <link>https://dev.to/jameswallis/adding-a-blog-with-a-dev-to-backend-to-a-static-next-js-website-with-canonical-urls-1i9g</link>
      <guid>https://dev.to/jameswallis/adding-a-blog-with-a-dev-to-backend-to-a-static-next-js-website-with-canonical-urls-1i9g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;For a shorter introduction (about half the length) check out &lt;a href="https://dev.to/jameswallis/i-completely-rewrote-my-personal-website-using-dev-to-as-a-cms-2pje"&gt;"I completely rewrote my personal website using Dev.to as a CMS"&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I've been &lt;a href="https://dev.to/jameswallis/20-000-views-milestone-what-i-ve-learnt-about-blogging-so-far-4laj"&gt;posting on Dev.to for a few months now&lt;/a&gt;. I love the platform, the editor, the ability to draft, edit and publish an article making it available to &lt;a href="https://dev.to/devteam/the-future-of-dev-160n"&gt;the millions of Dev.to users&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recently, I decided that I wanted to present them on &lt;a href="https://wallis.dev" rel="noopener noreferrer"&gt;my own website&lt;/a&gt;. After researching different ways to achieve this, I concluded using the Dev.to API to create the blog section of my website would be the perfect solution. I decided that articles would only show up on my website if I'd added a canonical URL to the article on Dev.to - meaning my website is seen as the source of the article (even though it was written on Dev.to).&lt;/p&gt;

&lt;p&gt;Continuing to use Dev.to also means that I don't need to configure storage for saving the articles or any images used. Additionally, I can take advantage of the built-in RSS feed which other blogging sites can read to automatically import my articles.&lt;/p&gt;

&lt;h3&gt;
  
  
  I came up with the following list of requirements:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the Dev.to API to fetch all my articles&lt;/strong&gt; and display them on my website.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetch and render each article at build time&lt;/strong&gt; to ensure the website would be fast and to ensure good SEO for the individual blog pages. Using dynamic pages would make the website load slower as it would query the Dev.to API on the client-side and also mean that I would have the same SEO data, such as page title, for each blog page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set the canonical URL of an article on Dev.to and have that be the article's URL on my website&lt;/strong&gt;. I wanted to continue to use the Dev.to editor to write and manage my articles, so they should only show on my website once I've added a canonical URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Have a &lt;em&gt;nice&lt;/em&gt; URL for the blog posts&lt;/strong&gt; on my website that I would be in complete control of. Neither the post ID nor the Dev.to path to the article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rebuild each time an article is created or updated&lt;/strong&gt;. This was crucial as the blog would be static - I didn't want to press the &lt;code&gt;rebuild&lt;/code&gt; each time I changed something.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;I was able to achieve all of this using a combination of Next.js dynamic pages, Vercel deploy hooks and the public Dev.to API.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting up the project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key technologies used
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; - if you prefer plain JavaScript for code examples, &lt;a href="https://github.com/james-wallis/dev-to-blog" rel="noopener noreferrer"&gt;this GitHub repository&lt;/a&gt; has the same functionality as described below but is purely JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;, React.js etc (required to create a Next.js app).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;, &lt;a href="https://github.com/tailwindlabs/tailwindcss-typography" rel="noopener noreferrer"&gt;Tailwind CSS Typography plugin&lt;/a&gt; (for styling).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/remarkjs/remark" rel="noopener noreferrer"&gt;Remark Markdown parser&lt;/a&gt; and plugins such as &lt;a href="https://github.com/remarkjs/remark-html" rel="noopener noreferrer"&gt;remark-html&lt;/a&gt; to convert the Markdown returned by the Dev.to API to HTML. Other plugins I use enable features such as &lt;a href="https://github.com/remarkjs/remark-highlight.js" rel="noopener noreferrer"&gt;code highlighting&lt;/a&gt;, &lt;a href="https://github.com/remarkjs/remark-gfm" rel="noopener noreferrer"&gt;GitHub flavour Markdown compatibility&lt;/a&gt; (for &lt;del&gt;strikethrough&lt;/del&gt; etc) and &lt;a href="https://github.com/remarkjs/remark-highlight.js" rel="noopener noreferrer"&gt;stripping out Front Matter from the displayed HTML&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://docs.dev.to/api/"&gt;Dev.to API&lt;/a&gt; and it's &lt;code&gt;https://dev.to/api/articles/me&lt;/code&gt; endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vercel.com/docs/more/deploy-hooks" rel="noopener noreferrer"&gt;Vercel deploy hooks&lt;/a&gt;. I use Vercel to host my Next.js site and their deploy hooks allow me to rebuild my website automatically when an article is added or edited on Dev.to.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To see all the packages I'm currently using on my website, &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/package.json" rel="noopener noreferrer"&gt;check out the &lt;code&gt;package.json&lt;/code&gt; on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two Next.js functions that run my website
&lt;/h3&gt;

&lt;p&gt;My personal website is built using Next.js. To ensure that all content continued to be generated at build time, I used two built-in Next.js functions that can be used to fetch data for pre-rendering. These are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation" rel="noopener noreferrer"&gt;&lt;code&gt;getStaticProps&lt;/code&gt;&lt;/a&gt; - fetch data from a source (think API or file) and pass it into the component via props.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation" rel="noopener noreferrer"&gt;&lt;code&gt;getStaticPaths&lt;/code&gt;&lt;/a&gt;- provides the ability to use dynamic routes with a static site. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll be using both functions to make the dynamic article page called &lt;code&gt;[slug].ts&lt;/code&gt; - the square brackets denote that it is a &lt;a href="https://nextjs.org/docs/routing/dynamic-routes" rel="noopener noreferrer"&gt;Next.js dynamic page&lt;/a&gt; and the name &lt;code&gt;slug&lt;/code&gt; is the name of the parameter that will be passed into &lt;code&gt;getStaticProps&lt;/code&gt; from &lt;code&gt;getStaticPaths&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I determine which articles appear on my website?
&lt;/h3&gt;

&lt;p&gt;For articles to appear on my website they have to have a canonical URL pointing at &lt;code&gt;https://wallis.dev/blog&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Whenever I refer to the page &lt;code&gt;slug&lt;/code&gt; I'm referring to the last section of the canonical URL (after &lt;code&gt;/blog&lt;/code&gt;). When reading the canonical URL from the Dev.to API I use the following function to convert the URL to the slug.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;websiteURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://wallis.dev/blog/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Takes a URL and returns the relative slug to your website&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertCanonicalURLToRelative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canonicalURL&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;canonicalURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;websiteURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I pass &lt;code&gt;https://wallis.dev/blog/a-new-article&lt;/code&gt; to &lt;code&gt;convertCanonicalURLToRelative&lt;/code&gt; it will return the &lt;code&gt;slug&lt;/code&gt; &lt;code&gt;a-new-article&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to add a blog with using Dev.to as a backend
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The individual article pages (&lt;code&gt;/blog/${slug}&lt;/code&gt;)
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Overview
&lt;/h4&gt;

&lt;p&gt;Each individual article page is generated at build time using the &lt;code&gt;getStaticPaths&lt;/code&gt; Next.js function that fetches all my Dev.to published articles, and saves them to a cache file. &lt;code&gt;getStaticProps&lt;/code&gt; then fetches an individual article from the cache and passes it into the page component via its props. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;A cache file must be used&lt;/em&gt; because &lt;a href="https://github.com/vercel/next.js/discussions/11272" rel="noopener noreferrer"&gt;Next.js doesn't allow passing data from &lt;code&gt;getStaticPaths&lt;/code&gt; to &lt;code&gt;getStaticProps&lt;/code&gt;&lt;/a&gt; - aside from the page &lt;code&gt;slug&lt;/code&gt;. For this reason, the page slug is used to fetch an article from the cache file.&lt;/p&gt;

&lt;h4&gt;
  
  
  Flow Diagram
&lt;/h4&gt;

&lt;p&gt;The diagram below should explain the process that is followed when creating dynamic pages through Next.js using the &lt;code&gt;getStaticPaths&lt;/code&gt; and &lt;code&gt;getStaticProps&lt;/code&gt; functions. It outlines the most important function calls, briefly explains what they do, and what is returned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqrvoolf5bhth0ri2lzd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqrvoolf5bhth0ri2lzd.jpg" alt="Article Page Diagram" width="787" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Implementation
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/pages/blog/%5Bslug%5D.tsx" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below you will find the code that dynamically creates each article page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../components/Layout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PageTitle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../components/PageTitle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../interfaces/IArticle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAllBlogArticles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getArticleFromCache&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/devto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.dev-to-cache.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ArticlePage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;IProps&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;
            &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coverImage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`Cover image for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md:mt-6 lg:mt-10 xl:mt-14 h-40 sm:h-48 md:h-52 lg:h-64 xl:h-68 2xl:h-80 mx-auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PageTitle&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;center&lt;/span&gt; &lt;span class="nx"&gt;icons&lt;/span&gt;&lt;span class="o"&gt;=&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="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mt-10 font-light leading-relaxed w-full flex flex-col items-center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prose dark:prose-dark lg:prose-lg w-full md:w-5/6 xl:w-9/12&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/section&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Layout&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;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="nl"&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="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Read cache and parse to object&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheContents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;cacheFile&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheContents&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Fetch the article from the cache&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getArticleFromCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="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="c1"&gt;// Get the published articles and cache them for use in getStaticProps&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IArticle&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="nf"&gt;getAllBlogArticles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Save article data to cache file&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;cacheFile&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the paths we want to pre-render based on posts&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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="o"&gt;=&amp;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;params&lt;/span&gt;&lt;span class="p"&gt;:&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="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// We'll pre-render only these paths at build time.&lt;/span&gt;
    &lt;span class="c1"&gt;// { fallback: false } means other routes should 404.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;paths&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;ArticlePage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow diagram above combined with the comments throughout the code should enable a full understanding of the code. If you have any questions, comment below.&lt;/p&gt;

&lt;p&gt;You'll notice that two functions are called from the &lt;code&gt;lib/dev.ts&lt;/code&gt; file. &lt;code&gt;getArticleFromCache&lt;/code&gt; does what it suggests, it finds an article in the cache and returns it. &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/lib/devto.ts#L60" rel="noopener noreferrer"&gt;&lt;code&gt;getAllBlogArticles&lt;/code&gt;&lt;/a&gt;, on the other hand, is the function that fetches all my articles from Dev.to and converts the supplied markdown into HTML - using functions from &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/lib/markdown.ts" rel="noopener noreferrer"&gt;&lt;code&gt;lib/markdown.ts&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/lib/devto.ts" rel="noopener noreferrer"&gt;Devto.ts&lt;/a&gt;
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AxiosResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../interfaces/IArticle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ICachedArticle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../interfaces/ICachedArticle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;convertMarkdownToHtml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sanitizeDevToMarkdown&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./markdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jameswallis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// My Dev.to username&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blogURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://wallis.dev/blog/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Prefix for article pages&lt;/span&gt;

&lt;span class="c1"&gt;// Takes a URL and returns the relative slug to your website&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertCanonicalURLToRelative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blogURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Takes the data for an article returned by the Dev.to API and:&lt;/span&gt;
&lt;span class="c1"&gt;// * Parses it into the IArticle interface&lt;/span&gt;
&lt;span class="c1"&gt;// * Converts the full canonical URL into a relative slug to be used in getStaticPaths&lt;/span&gt;
&lt;span class="c1"&gt;// * Converts the supplied markdown into HTML (it does a little sanitising as Dev.to allows markdown headers (##) with out a trailing space&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertDevtoResponseToArticle&lt;/span&gt; &lt;span class="o"&gt;=&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="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;convertCanonicalURLToRelative&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;canonical_url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;markdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitizeDevToMarkdown&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;body_markdown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;convertMarkdownToHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// parse into article object&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Filters out any articles that are not meant for the blog page&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blogFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blogURL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Get all users articles from Dev.to&lt;/span&gt;
&lt;span class="c1"&gt;// Use the authenticated Dev.to article route to get the article markdown included&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getAllArticles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;per_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEVTO_APIKEY&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="nx"&gt;AxiosResponse&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://dev.to/api/articles/me`&lt;/span&gt;&lt;span class="p"&gt;,&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;headers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&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;convertDevtoResponseToArticle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Get all articles from Dev.to meant for the blog page&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getAllBlogArticles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAllArticles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blogFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Get my latest published article meant for the blog (and portfolio) pages&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLatestBlogAndPortfolioArticle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAllArticles&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;latestBlog&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blogFilter&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;latestPortfolio&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;portfolioFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ignore this! It's meant for another page (see the wallis.dev GitHub repository for more information)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;latestBlog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latestPortfolio&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Gets an article from Dev.to using the ID that was saved to the cache earlier&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getArticleFromCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ICachedArticle&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get minified post from cache&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedArticle&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cachedArticle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;article&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 key points to note about the &lt;code&gt;devto.ts&lt;/code&gt; file is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I've used the authenticated &lt;code&gt;https://dev.to/api/articles/me&lt;/code&gt; endpoint to fetch all my articles from Dev.to. This endpoint is the only one that returns all my articles (ok, 1000 max...) and includes the article markdown. Authenticating also gives a slightly higher API limit. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Previously I used the built-in HTML returned in the &lt;code&gt;https://dev.to/api/articles/{id}&lt;/code&gt; but I kept hitting the API limit as each build made as many API calls as I had articles&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Get a Dev.to API Token following the &lt;a href="https://docs.forem.com/api/#section/Authentication/api_key" rel="noopener noreferrer"&gt;instructions on the API docs&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;convertDevtoResponseToArticle&lt;/code&gt; function converts the markdown into HTML using a function from the &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/lib/markdown.ts" rel="noopener noreferrer"&gt;&lt;code&gt;lib/markdown.ts&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  &lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/lib/markdown.ts" rel="noopener noreferrer"&gt;Markdown.ts&lt;/a&gt;
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;unified&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unified&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remark-parse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;remarkHtml&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remark-html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;highlight&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remark-highlight.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;gfm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remark-gfm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;matter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gray-matter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;stripHtmlComments&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;strip-html-comments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Corrects some Markdown specific to Dev.to&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sanitizeDevToMarkdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;correctedMarkdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Dev.to sometimes turns "# header" into "#&amp;amp;nbsp;header"&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replaceSpaceCharRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;g&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;correctedMarkdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;replaceSpaceCharRegex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Dev.to allows headers with no space after the hashtag (I don't use # on Dev.to due to the title)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addSpaceAfterHeaderHashtagRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/##&lt;/span&gt;&lt;span class="se"&gt;(?=[&lt;/span&gt;&lt;span class="sr"&gt;a-z|A-Z&lt;/span&gt;&lt;span class="se"&gt;])&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;correctedMarkdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addSpaceAfterHeaderHashtagRegex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$&amp;amp; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Converts given markdown into HTML&lt;/span&gt;
&lt;span class="c1"&gt;// Splits the gray-matter from markdown and returns that as well&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertMarkdownToHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unified&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gfm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Allow GitHub flavoured markdown&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Add code highlighting&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remarkHtml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Convert to HTML&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stripHtmlComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is pretty simple; the comments should explain everything, so I won't add anything more. If you'd like to learn more about using Remark converts with Next.js, you can read my blog titled &lt;a href="https://dev.to/jameswallis/how-to-use-the-remark-markdown-converters-with-next-js-projects-a8a"&gt;"How to use the Remark Markdown converters with Next.js projects"&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;Phew, that was a lot. Hopefully, I didn't lose you in the code examples and explanations!&lt;/p&gt;

&lt;p&gt;Everything above explains how I've built the dynamic article pages on my website. I've included all the code that you'll need to create the dynamic blog pages on your own website.&lt;/p&gt;

&lt;p&gt;By the way, when the code above is compiled it produces an article page such as &lt;a href="https://wallis.dev/blog/nextjs-serverside-data-fetching" rel="noopener noreferrer"&gt;https://wallis.dev/blog/nextjs-serverside-data-fetching&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewatlfueoo0dqqbxp8zz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewatlfueoo0dqqbxp8zz.png" alt="article page screenshot" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's move onto the blog overview page (&lt;a href="https://wallis.dev/blog" rel="noopener noreferrer"&gt;wallis.dev/blog&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  The article overview page (&lt;code&gt;/blog&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Building a page for each of your Dev.to articles at build time is great but how will a user find them without an overview page?! They probably won't!&lt;/p&gt;

&lt;h4&gt;
  
  
  Overview
&lt;/h4&gt;

&lt;p&gt;The overview page is much simpler than the dynamic article pages and only uses functions from the &lt;code&gt;lib/devto.ts&lt;/code&gt; file introduced above. So this section will be shorter than the last.&lt;/p&gt;

&lt;h4&gt;
  
  
  Flow Diagram
&lt;/h4&gt;

&lt;p&gt;As before, I've made a diagram to display the process followed when displaying all the article summaries on the overview page. You'll notice that this time I'm only using &lt;code&gt;getStaticProps&lt;/code&gt; rather than &lt;code&gt;getStaticProps&lt;/code&gt; and &lt;code&gt;getStaticPaths&lt;/code&gt;. This is because I'm only loading data for one page rather than creating dynamic pages (which is what &lt;code&gt;getStaticPaths&lt;/code&gt; allows you to do).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdbyc2vteef4rsmkl74s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdbyc2vteef4rsmkl74s.jpg" alt="Overview page diagram" width="781" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Implementation
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/james-wallis/wallis.dev/blob/master/pages/blog.tsx" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/Layout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PageTitle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/PageTitle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Section&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/Section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ArticleCard&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/ArticleCard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../interfaces/IArticle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAllBlogArticles&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lib/devto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IArticle&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blog ✍️&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I share anything that may help others, technologies I&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;'m using and cool things I&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;'ve made.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BlogPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;IProps&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PageTitle&lt;/span&gt;
            &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;subtitle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Section&lt;/span&gt; &lt;span class="nx"&gt;linebreak&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;articles&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;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canonical&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ArticleCard&lt;/span&gt;
                    &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="p"&gt;))}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Section&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Layout&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;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="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get all the articles that have a canonical URL pointed to your blog&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAllBlogArticles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Pass articles to the page via props&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;BlogPage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essentially the above code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loads the articles from the Dev.to API&lt;/li&gt;
&lt;li&gt;Passes them into the component&lt;/li&gt;
&lt;li&gt;Maps over each article and creates a summary card for each which links to the dynamic article page created in the previous step.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The overview page looks like this:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwxqpqfdbldwky0v8zd2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwxqpqfdbldwky0v8zd2.png" alt="Overview Page screenshot" width="800" height="586"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;Amazing, that's the overview page complete! If you're following along you should now have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Blog pages being created dynamically&lt;/li&gt;
&lt;li&gt;An overview page that links to the dynamic blog pages&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Rebuild each time an article is created or updated
&lt;/h3&gt;

&lt;p&gt;The final step that I took to create my Dev.to powered website is to set up a Vercel deploy hook. My website is hosted on Vercel so I am able to use a deploy hook to programmatically trigger a rebuild, refreshing the article content in the process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deploy Hooks allow you to create URLs that accept HTTP POST requests in order to trigger deployments and re-run the Build Step.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://vercel.com/docs/more/deploy-hooks" rel="noopener noreferrer"&gt;https://vercel.com/docs/more/deploy-hooks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;To trigger the deploy hook, I have created a &lt;a href="https://docs.forem.com/api/#tag/webhooks" rel="noopener noreferrer"&gt;Dev.to API webhook&lt;/a&gt; that calls it each time an article is created or updated.&lt;/p&gt;
&lt;h4&gt;
  
  
  Configuring the automatic rebuild
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;A prereq for this section is that you're website needs to be deployed onto Vercel. &lt;a href="https://dev.to/jameswallis/deploying-a-next-js-project-on-vercel-in-less-than-three-minutes-with-a-custom-domain-568o"&gt;I've created instructions on how to do this&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To create a deploy hook, follow &lt;a href="https://vercel.com/docs/more/deploy-hooks" rel="noopener noreferrer"&gt;the Vercel documentation&lt;/a&gt; - it's a lot more simple than you'd think.&lt;/p&gt;

&lt;p&gt;Once you have the deploy URL we can use the Dev.to API to create a webhook to trigger it.&lt;/p&gt;

&lt;p&gt;You can do this using &lt;code&gt;curl&lt;/code&gt; (make sure you add your API_KEY and change the &lt;code&gt;target_url&lt;/code&gt; to be your Vercel deploy hook URL):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST -H "Content-Type: application/json" \
  -H "api-key: API_KEY" \
  -d '{"webhook_endpoint":{"target_url":"https://example.org/webhooks/webhook1","source":"DEV","events":["article_created", "article_updated"]}}' \
  https://dev.to/api/webhooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.forem.com/api/#operation/createWebhook" rel="noopener noreferrer"&gt;For more information, see the Dev.to API docs&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;Nice one, now your website will automatically redeploy each time you create or update an article on Dev.to!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;I love my website right now and using Dev.to to manage most of its content has made adding content much more efficient than previously. However, there are a couple of things I want to improve in the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a user is viewing a blog on Dev.to and it links to another of my articles, the user should stay on Dev.to. But if they're on &lt;a href="https://wallis.dev" rel="noopener noreferrer"&gt;wallis.dev&lt;/a&gt;, they should stay on it rather than being taken to Dev.to.&lt;/li&gt;
&lt;li&gt;Another Dev.to user made a comment in another of my articles and made the point that if Dev.to suddenly turned off, I'd lose my articles. However unlikely, I want to set up a system to take daily backups of my articles to mitigate the risk of losing them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Round up
&lt;/h2&gt;

&lt;p&gt;In this article, I've taken you through the code that allows Dev.to to power my website. If you venture onto my GitHub you'll see that in addition to having a blog section (&lt;a href="https://wallis.dev/blog" rel="noopener noreferrer"&gt;https://wallis.dev/blog&lt;/a&gt;), I also use Dev.to to display my portfolio entries (&lt;a href="https://wallis.dev/portfolio" rel="noopener noreferrer"&gt;https://wallis.dev/portfolio&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;If you want more background on why and how I've used the Dev.to API to power my website, &lt;a href="https://dev.to/jameswallis/i-completely-rewrote-my-personal-website-using-dev-to-as-a-cms-2pje"&gt;read my initial post discussing it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you found this article interesting or it has helped you to use Next.js and the Dev.to API to build your own website using Dev.to as a CMS, drop me a reaction or let me know in the comments!&lt;/p&gt;

&lt;p&gt;Anything I can improve? Let me know in the comments.&lt;/p&gt;

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

&lt;p&gt;PS, I'm currently deciding whether I should create a tutorial series that will take you through building a Dev.to powered blog from scratch - is this something you would read/follow?&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Different ways to fetch data in Next.js (server-side) and when to use them</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Tue, 02 Mar 2021 23:48:09 +0000</pubDate>
      <link>https://dev.to/jameswallis/different-ways-to-fetch-data-in-next-js-server-side-and-when-to-use-them-1jb0</link>
      <guid>https://dev.to/jameswallis/different-ways-to-fetch-data-in-next-js-server-side-and-when-to-use-them-1jb0</guid>
      <description>&lt;p&gt;When building an application powered by Next.js it's probable that you'll need to fetch data from either a file, an internal API route or an external API &lt;a href="https://dev.to/jameswallis/i-completely-rewrote-my-personal-website-using-dev-to-as-a-cms-2pje"&gt;such as the Dev.to API&lt;/a&gt;. Moreover, determining what data fetching method to use in a Next.js application can easily become confusing - especially as it isn't as simple as making an API request inside your components render function, as you might in a stock React app.&lt;/p&gt;

&lt;p&gt;The following guide will help you carefully select the server-side data fetching method that suits your app (FYI you can use multiple methods in a single app). For each method, I have outlined when it runs, it's benefits and an example of when you could use the method in your Next.js application.&lt;/p&gt;

&lt;p&gt;The following methods fetch data either at build time or on each request before the data is sent to the client.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation" rel="noopener noreferrer"&gt;getStaticProps (Static Generation)&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Fetch data at &lt;strong&gt;build time&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;getStaticProps&lt;/code&gt; method can be used inside a page to fetch data at build time, e.g. when you run &lt;code&gt;next build&lt;/code&gt;. Once the app is built, it won't refresh the data until another build has been run.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Added in Next 9.3&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Usage:
&lt;/h4&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;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://.../data`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&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;notFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="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="c1"&gt;// will be passed to the page component as props&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;h4&gt;
  
  
  Benefits:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;It enables the page to be statically generated and will produce fast load times of all the data fetching methods.&lt;/li&gt;
&lt;li&gt;If each page in your app uses &lt;code&gt;getStaticProps&lt;/code&gt; (or no server-side data fetching methods) then Next.js will be able to export it into static HTML using &lt;a href="https://nextjs.org/docs/advanced-features/static-html-export" rel="noopener noreferrer"&gt;&lt;code&gt;next export&lt;/code&gt;&lt;/a&gt;. This is advantageous if you want to create a static site that can be hosted on places &lt;a href="https://dev.to/jameswallis/deploying-a-next-js-app-to-github-pages-24pn"&gt;such as GitHub Pages&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The data is rendered before it reaches the client - great for SEO.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Example usage:
&lt;/h4&gt;

&lt;p&gt;Imagine you have a personal blog site that renders pages from markdown files at build time. &lt;code&gt;getStaticProps&lt;/code&gt; can read the files and pass the data into the page component at build time. When you make a change to a blog entry, you rebuild the site to see the changes. &lt;a href="https://ameira.me" rel="noopener noreferrer"&gt;ameira.me&lt;/a&gt;, a site I built, uses this method - each time Ameira makes a change to her portfolio, Vercel automatically rebuilds and republishes the site.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering" rel="noopener noreferrer"&gt;getServerSideProps (Server-side Rendering)&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Fetch data on &lt;strong&gt;each request&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;getServerSideProps&lt;/code&gt; method fetches data each time a user requests the page. It will fetch the data before sending the page to the client (as opposed to loading the page and fetching the data on the client-side). If the client makes a subsequent request, the data will be fetched again.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Added in Next 9.3&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Usage:
&lt;/h4&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;getServerSideProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://...`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&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;notFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="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="c1"&gt;// will be passed to the page component as props&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;h4&gt;
  
  
  Benefits:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The data is refreshed each time a client loads the page meaning that it is up to date as of when they visit the page.&lt;/li&gt;
&lt;li&gt;The data is rendered before it reaches the client - great for SEO.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Example usage:
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;getServerSideProps&lt;/code&gt; is perfect for building an application that requires the client to see the most up to date information, but isn't refreshed while the client is on the page (see client-side for constantly updating information). For example, if I had a page on my personal site that displayed information about my last GitHub commit or my current Dev.to stats, I'd want these fetched each time a page is viewed.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://nextjs.org/docs/api-reference/data-fetching/getInitialProps" rel="noopener noreferrer"&gt;getInitialProps (Server-side Rendering)&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Fetch data on &lt;strong&gt;each request&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;getInitialProps&lt;/code&gt; was the original way to fetch data in a Next.js app on the server-side. As of Next.js 9.3 you should use the previously discussed methods over &lt;code&gt;getInitialProps&lt;/code&gt; but I'm including it in this article because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It can still be used - although the Next.js maintainers advise you not to as &lt;code&gt;getStaticProps&lt;/code&gt; and &lt;code&gt;getServerSideProps&lt;/code&gt; enable you to choose from static or server-side data fetching.&lt;/li&gt;
&lt;li&gt;Knowing about &lt;code&gt;getInitialProps&lt;/code&gt; helps when you come across an old Stack Overflow query that mentions it, and also that you shouldn't just copy and paste that solution!.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Note: If you're on Next.js 9.3 or above, use the two methods above.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Usage:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;stars&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Next stars: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stars&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getInitialProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.github.com/repos/vercel/next.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;stars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stargazers_count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Benefits:
&lt;/h4&gt;

&lt;p&gt;Same as &lt;code&gt;getServerSideProps&lt;/code&gt; - use &lt;code&gt;getServerSideProps&lt;/code&gt;!&lt;/p&gt;

&lt;h4&gt;
  
  
  Example usage:
&lt;/h4&gt;

&lt;p&gt;Same as &lt;code&gt;getServerSideProps&lt;/code&gt; - use &lt;code&gt;getServerSideProps&lt;/code&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  How to decide which one to use?
&lt;/h3&gt;

&lt;p&gt;When using Next.js, I always aim to make each page static. This means that I try to avoid using &lt;code&gt;getServerSideProps&lt;/code&gt; and favour &lt;code&gt;getStaticProps&lt;/code&gt;. However, if the data that I am fetching changes often then of course I will use &lt;code&gt;getServerSideProps&lt;/code&gt;. I never use &lt;code&gt;getInitialProps&lt;/code&gt; anymore.&lt;/p&gt;

&lt;p&gt;So normally I try &lt;code&gt;getStaticProps&lt;/code&gt; and if that is causing data to become outdated then I move to &lt;code&gt;getServerSideProps&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A word on client-side data fetching
&lt;/h3&gt;

&lt;p&gt;This article hasn't covered any client-side data fetching methods but you can use the &lt;code&gt;useEffect&lt;/code&gt; hook to make the request or the &lt;a href="https://swr.vercel.app" rel="noopener noreferrer"&gt;&lt;code&gt;useSwr&lt;/code&gt;&lt;/a&gt; custom hook made by Vercel engineers which implements &lt;code&gt;stale-while-revalidate&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;- &lt;a href="https://swr.vercel.app" rel="noopener noreferrer"&gt;swr.vercel.app&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  SWR Usage:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useSWR&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;swr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Profile&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="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSWR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;failed to load&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;hello &lt;span class="si"&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;In this article, I've introduced three Next.js methods that can be used to fetch data either at build time or before each client request.&lt;/p&gt;

&lt;p&gt;Liked this article? Hit the like button!&lt;/p&gt;

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

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>5 APIs to use in your next project</title>
      <dc:creator>James Wallis</dc:creator>
      <pubDate>Tue, 23 Feb 2021 23:28:30 +0000</pubDate>
      <link>https://dev.to/jameswallis/5-apis-to-use-in-your-next-project-575o</link>
      <guid>https://dev.to/jameswallis/5-apis-to-use-in-your-next-project-575o</guid>
      <description>&lt;p&gt;Being a developer is great - you can build &lt;em&gt;almost anything&lt;/em&gt; you want! Sometimes, however, it's difficult to decide what to build next as there are so many different project ideas out there. To help you find your next project, I've come up with a list of APIs that you can play with and provided potential project ideas.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://docs.dev.to/api"&gt;Dev.to&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Using Dev.to's own API you could create your own personal editor that can create and publish new posts as well as edit old ones. While creating this app you'd have to build a markdown editor and a system to publish your articles to Dev.to.&lt;/p&gt;

&lt;p&gt;Additionally, you could use the Dev.to API to dynamically display your articles on your own personal website. &lt;a href="https://dev.to/jameswallis/i-completely-rewrote-my-personal-website-using-dev-to-as-a-cms-2pje"&gt;In fact, I recently rewrote my website to use Dev.to as a CMS to make the blog and portfolio sections easier to manage.&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://unsplash.com/developers" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Unsplash is a website that hosts "beautiful, free images and photos that you can download and use for any project". Its API can be used to fetch photos from Unsplash.&lt;/p&gt;

&lt;p&gt;The Unsplash API could be used to make an application with a background that changes to a random image at different times during the day. For example, you could build your own Chrome new tab page that has a beautiful changing background and contains links that you find useful. Bonus points if the background reflects the time of day e.g. light during the day and darker at night.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://developer.spotify.com/discover/" rel="noopener noreferrer"&gt;Spotify API&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The Spotify API is one I've used in the past to create a home automation dashboard that shows what song I'm currently playing and has controls to play, pause, skip tracks and set the volume (in addition to other functionality). In addition to these features, Spotify also has APIs that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide audio analysis to learn about a tracks danceability, energy, valence, and more&lt;/li&gt;
&lt;li&gt;Control playback and can use the Web SDK to play full tracks&lt;/li&gt;
&lt;li&gt;Customize and display a user's recommendations&lt;/li&gt;
&lt;li&gt;Search for tracks in a user's region or anywhere&lt;/li&gt;
&lt;li&gt;For others and further app ideas check the &lt;a href="https://developer.spotify.com/discover/" rel="noopener noreferrer"&gt;discover page&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://developers.meethue.com" rel="noopener noreferrer"&gt;Hue&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you have any products from the Phillips Hue family, it's likely that they can be controlled using the Hue API. The official app can become a bit busy when you have multiple devices, so you could build an app that can control Hue bulbs around the house, changing their colour and brightness. You could also go back to basics and build a command-line app to control your lights!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://api.slack.com" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Using the Slack API you can build tools that integrate directly with Slack. The Slack API can be used to build a chatbot that can carry out various functions such as setting reminders, checking the weather or communicating with other APIs I've introduced above - for example, a chatbot that can control your Hue lights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus - A Dashboard to rule them all
&lt;/h2&gt;

&lt;p&gt;If you're looking for a larger project to take on, you could combine all the APIs I've listed above to create a dashboard that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display your total views/reactions using the Dev.to API&lt;/li&gt;
&lt;li&gt;Have a beautiful background using the Unsplash API&lt;/li&gt;
&lt;li&gt;Control your currently playing track and volume level through the Spotify API.&lt;/li&gt;
&lt;li&gt;Display the state of lights around your house and control them using the Hue API.&lt;/li&gt;
&lt;li&gt;Integrate the Slack API to make a chatbot that can directly control the whole dashboard!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;These are just a few services that have APIs that you can use to build your next project. Hopefully, this article has given you that little bit of inspiration you needed to develop your next app!&lt;/p&gt;

&lt;p&gt;Used any of these APIs before or have any to suggest? Pop them in the comments.&lt;/p&gt;

&lt;p&gt;Liked this article? Hit the like button!&lt;/p&gt;

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

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>api</category>
    </item>
  </channel>
</rss>
