<?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: Daniel Starner</title>
    <description>The latest articles on DEV Community by Daniel Starner (@dan_starner).</description>
    <link>https://dev.to/dan_starner</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%2F7634%2Fc06c2400-ad52-4b77-aa4f-247e768b2bcb.png</url>
      <title>DEV Community: Daniel Starner</title>
      <link>https://dev.to/dan_starner</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dan_starner"/>
    <language>en</language>
    <item>
      <title>Serve Static Internal Documentation Behind OAuth Authentication</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Sat, 02 Jul 2022 04:31:33 +0000</pubDate>
      <link>https://dev.to/dan_starner/easily-serve-internal-documentation-behind-oauth-authentication-322k</link>
      <guid>https://dev.to/dan_starner/easily-serve-internal-documentation-behind-oauth-authentication-322k</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Engineering teams frequently have centralized documentation websites that tie together all the different teams, projects, and workflows supported internally by the company. &lt;a href="https://dev.to/dan_starner/keeping-documentation-debt-at-bay-clients-happy-53o"&gt;I have spoken about the importance of quality documentation before&lt;/a&gt;, and why its important to present internal documentation in an easy-to-read, easy-to-search manner.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/dan_starner" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F7634%2Fc06c2400-ad52-4b77-aa4f-247e768b2bcb.png" alt="dan_starner"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/dan_starner/keeping-documentation-debt-at-bay-clients-happy-53o" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Maintaining Quality Documentation&lt;/h2&gt;
      &lt;h3&gt;Daniel Starner ・ Sep 9 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#codequality&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#productivity&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#development&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ux&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;In that post, I talked about how my team ended up writing our documentation using &lt;a href="https://docusaurus.io/" rel="noopener noreferrer"&gt;Docusaurus&lt;/a&gt;, a React- &amp;amp; Markdown-driven static site generator. It suited our needs well, looked great, and was &lt;em&gt;very&lt;/em&gt; easy to write customized documentation and components using the &lt;a href="https://mdxjs.com/" rel="noopener noreferrer"&gt;MDX&lt;/a&gt; features.&lt;/p&gt;

&lt;p&gt;We hosted this documentation site internally, with no authentication required since viewing it needed a user to be on &lt;a href="https://www.paloaltonetworks.com/cyberpedia/what-is-a-business-vpn-understand-its-uses-and-limitations" rel="noopener noreferrer"&gt;the company VPN&lt;/a&gt; already.&lt;/p&gt;

&lt;p&gt;I wanted to bring this same documentation style over to other companies, but they did not enforce the same VPN restrictions as Bloomberg. With this constraint, we needed a way to force authentication to our Docusaurus site before showing content to users. Authentication &amp;amp; private content was &lt;a href="https://github.com/facebook/docusaurus/issues/2769" rel="noopener noreferrer"&gt;already discussed&lt;/a&gt; a few times on the project repository, but was ultimately rejected since that is outside the scope of a static-site generator.&lt;/p&gt;

&lt;p&gt;So what can we do?&lt;/p&gt;

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

&lt;p&gt;Let's walk through how to deploy Docusaurus behind an OAuth proxy which will force users to log in with a 3&lt;sup&gt;rd&lt;/sup&gt; party provider before viewing our documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tools &amp;amp; Concepts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Our documentation will be generated using the &lt;a href="https://docusaurus.io/" rel="noopener noreferrer"&gt;Docusaurus&lt;/a&gt; static-site generator&lt;/li&gt;
&lt;li&gt;The site will be protected using GitHub sign-in by fronting it with &lt;a href="https://oauth2-proxy.github.io/oauth2-proxy/" rel="noopener noreferrer"&gt;Oauth2 Proxy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The site will be hosted on &lt;a href="https://www.heroku.com" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt; for ease, but you can change this for your specific project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is Oauth?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://oauth.net/" rel="noopener noreferrer"&gt;Oauth&lt;/a&gt; (&lt;strong&gt;O&lt;/strong&gt;pen &lt;strong&gt;Auth&lt;/strong&gt;orization) is an authorization protocol that allows a user to authenticate and access one service by allowing another service to provide your basic account details. OAuth allows for password-less logins - which are inherently safer - and requires users to maintain fewer accounts &amp;amp; credentials across services.&lt;/p&gt;

&lt;p&gt;An example of Oauth is when a website allows you to log in with your Google, Twitter, or GitHub credentials. The site has preconfigured routes, configurations, and protocols to interact with those 3&lt;sup&gt;rd&lt;/sup&gt; party providers, so you can easily log in without needing credentials.&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%2Fepr2zxlgb286iq75t8n8.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%2Fepr2zxlgb286iq75t8n8.png" alt="Oauth Example Login" width="779" height="833"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our example will allow users to log in with their GitHub account to view the documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Process
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;This was performed on Docusaurus &lt;code&gt;2.0.0-beta.21&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With some background on what we are doing, let's dive into setting up the project. This will walk through the individual steps, but you can view &lt;a href="https://github.com/dstarner/oauth-gated-docusaurus" rel="noopener noreferrer"&gt;the complete starter project&lt;/a&gt; if you want to just get something deployed.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/dstarner" rel="noopener noreferrer"&gt;
        dstarner
      &lt;/a&gt; / &lt;a href="https://github.com/dstarner/oauth-gated-docusaurus" rel="noopener noreferrer"&gt;
        oauth-gated-docusaurus
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Running Docusaurus gated behind an OAuth provider session
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Creating the Docusaurus Project
&lt;/h3&gt;

&lt;p&gt;To start, we will create a basic Docusaurus project. This guide assumes you already have Node 16.x and &lt;code&gt;npm&lt;/code&gt; installed on your machine, but you can use &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;the &lt;code&gt;nvm&lt;/code&gt; tool&lt;/a&gt; to configure these if needed.&lt;/p&gt;

&lt;p&gt;Create a new Docusaurus site with the &lt;code&gt;classic&lt;/code&gt; (default) template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-docusaurus@latest oauth-gated-docusaurus classic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Assume that the rest of the steps are run inside this newly created &lt;code&gt;oauth-gated-docusaurus&lt;/code&gt; directory.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Docs-Only Mode
&lt;/h3&gt;

&lt;p&gt;Since this will be internal documentation only, I usually delete the &lt;code&gt;blog&lt;/code&gt; &amp;amp; homepage and clean up the &lt;code&gt;docusaurus.config.js&lt;/code&gt; to match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; blog src/pages src/components/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, replace the &lt;code&gt;presets&lt;/code&gt; key in the &lt;code&gt;docusaurus.config.js&lt;/code&gt; file with the following:&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="nx"&gt;presets&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;classic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="cm"&gt;/** @type {import('@docusaurus/preset-classic').Options} */&lt;/span&gt;
      &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;sidebarPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./sidebars.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;routeBasePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Serve the docs at the site's root&lt;/span&gt;
          &lt;span class="c1"&gt;// Please change this to your repo.&lt;/span&gt;
          &lt;span class="c1"&gt;// Remove this to remove the "edit this page" links.&lt;/span&gt;
          &lt;span class="na"&gt;editUrl&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://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;blog&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="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;customCss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/css/custom.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;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;Make sure to update the GitHub URL(s) to point to your repository instead.&lt;/p&gt;

&lt;p&gt;Finally, create a landing page for your blog site. The &lt;code&gt;slug: /&lt;/code&gt; denotes this file as the root URL. It is the only file that should have the &lt;code&gt;slug&lt;/code&gt; key defined.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt;&amp;gt; docs/index.md
---
sidebar_position: 1
sidebar_label: Homepage
slug: /
---

# Welcome to Our Documentation

Hello world!
&lt;/span&gt;&lt;span class="no"&gt;
EOT
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you should be able to &lt;code&gt;npm start&lt;/code&gt; the server and open up the URL Docusaurus shows in the immediate output.&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%2Fq8nnpdliw8mzdwr6mk1j.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%2Fq8nnpdliw8mzdwr6mk1j.png" alt="Docusaurus Running" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try to run &lt;code&gt;npm run build&lt;/code&gt;, you may encounter a few build errors due to invalid URLs. This is left to an exercise for the user, but the example repository already has the fixes. It comes down to removing the &lt;code&gt;to: blog&lt;/code&gt; and replacing &lt;code&gt;/docs/intro&lt;/code&gt; with &lt;code&gt;/intro&lt;/code&gt; to get the project to build nicely.&lt;/p&gt;

&lt;p&gt;If you do not want to fix these issues, you can also set &lt;code&gt;onBrokenLinks: 'warn'&lt;/code&gt; if you like. See the &lt;a href="https://docusaurus.io/docs/api/docusaurus-config#onBrokenLinks" rel="noopener noreferrer"&gt;&lt;code&gt;onBrokenLinks&lt;/code&gt; documentation here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Basic Version to Heroku
&lt;/h3&gt;

&lt;p&gt;Before adding in the authentication and authorization components, we will get the basic site up on &lt;a href="https://heroku.com" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;, so we have a URL to redirect back to when configuring our OAuth application later.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create the Heroku app. Create a &lt;a href="https://heroku.com" rel="noopener noreferrer"&gt;Heroku account&lt;/a&gt; and &lt;a href="https://devcenter.heroku.com/articles/heroku-cli" rel="noopener noreferrer"&gt;install the Heroku CLI&lt;/a&gt; as needed.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku create
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the &lt;code&gt;nodejs&lt;/code&gt; buildpack which will run &lt;code&gt;npm build&lt;/code&gt; automatically before deployment&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku buildpacks:add heroku/nodejs
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a &lt;code&gt;Procfile&lt;/code&gt; to run Docusaurus in a web process&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt;&amp;gt; Procfile
web: npm run serve -- -p &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;PORT
&lt;/span&gt;&lt;span class="no"&gt;EOT
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Commit the changes and push them to GitHub &amp;amp; Heroku&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"prepare for heroku deployment"&lt;/span&gt;
git push heroku main &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With the following command, you should be able to view your documentation in the browser, but there's one issue...it's still public! So let's fix that with some OAuth and a proxy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating the GitHub OAuth Credentials
&lt;/h3&gt;

&lt;p&gt;With Docusaurus configured and running locally, we can begin creating an OAuth application. We will be using GitHub as an application provider, meaning users will log in via it, redirecting them back to our documentation. I chose GitHub because I assume &lt;em&gt;most&lt;/em&gt; people reading this article have a GitHub account, and any GitHub user can create OAuth applications without paying.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit &lt;a href="https://github.com/settings/applications/new" rel="noopener noreferrer"&gt;the page to register a new app&lt;/a&gt; and provide the required details.

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Application Name&lt;/strong&gt;: &lt;code&gt;Docusaurus OAuth Example&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Homepage URL&lt;/strong&gt;: Whatever &lt;code&gt;heroku open&lt;/code&gt; opened in your browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization callback URL&lt;/strong&gt;: &lt;code&gt;&amp;lt;Homepage URL &amp;gt;/oauth2/callback&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&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%2Fgf0lc3qu93g9xab3ytm3.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%2Fgf0lc3qu93g9xab3ytm3.png" alt="Register the GitHub OAuth App" width="594" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the OAuth application is registered, note the &lt;strong&gt;Client ID&lt;/strong&gt;, which should be a random-looking string of 16-24 characters. Save this to Heroku to be used later with the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku config:set &lt;span class="nv"&gt;OAUTH2_PROXY_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;value from GitHub&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Generate a Client Secret
&lt;/h4&gt;

&lt;p&gt;Finally, you will need to generate a client secret for the OAuth application. Under the &lt;strong&gt;Client secrets&lt;/strong&gt; section, click the &lt;strong&gt;Generate a new client secret&lt;/strong&gt; button, which will reload the page with a newly minted client secret key. &lt;strong&gt;Make sure to copy it manually or by clicking the Copy icon because you cannot retrieve this key again!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Save this to the Heroku app to be used later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku config:set &lt;span class="nv"&gt;OAUTH2_PROXY_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;value from GitHub&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding OAuth Proxy to Docusaurus
&lt;/h3&gt;

&lt;p&gt;Finally, all of the pieces are in place to run our documentation site behind an OAuth2 Proxy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Proxy is a middleman between users trying to access the website and the web process hosting the content. Its job is to either show the website content to authorized users or redirect those not authorized to GitHub.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Begin by adding &lt;a href="https://elements.heroku.com/buildpacks/cfra/heroku-buildpack-oauth2-proxy" rel="noopener noreferrer"&gt;the &lt;code&gt;heroku-buildpack-oauth2-proxy&lt;/code&gt; community buildpack&lt;/a&gt; to your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku buildpacks:add cfra/oauth2-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added the GitHub OAuth client and secret keys in the last step, but we need to tell our proxy to use GitHub for authentication. We can do this with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku config:set &lt;span class="nv"&gt;OAUTH2_PROXY_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;github
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step is to generate a secret key to sign all authentication cookies. This string can be random, so we are using Python for this step. You may need to switch &lt;code&gt;python&lt;/code&gt; for &lt;code&gt;python3&lt;/code&gt; in the command if you get a Python error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku config:set &lt;span class="nv"&gt;OAUTH2_PROXY_COOKIE_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s1"&gt;'from secrets import token_urlsafe; print(token_urlsafe(32)[:32])'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see the change, you will need to place the oauth2-proxy script &lt;em&gt;in front&lt;/em&gt; of our &lt;code&gt;npm serve ...&lt;/code&gt; command from earlier. Then, update the Procfile to the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;web: /app/bin/start_with_oauth2_proxy.sh npm run serve &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the server is now running on &lt;code&gt;8080&lt;/code&gt; instead of looking at the &lt;code&gt;$PORT&lt;/code&gt; variable. This change is because &lt;code&gt;oauth2_proxy&lt;/code&gt; runs listening on &lt;code&gt;$PORT,&lt;/code&gt; and it expects our gated webserver to listen on &lt;code&gt;8080&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Login
&lt;/h3&gt;

&lt;p&gt;View your documentation in the browser again, and you should be greeted with the OAuth2 Proxy landing page. Try to sign in with GitHub now!&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%2F3fl08ttzqwb7osqdx8ns.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%2F3fl08ttzqwb7osqdx8ns.png" alt="OAuth is required to view" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If all worked, you should be able to view your documentation again! We have successfully gated our documentation behind an OAuth Provider.&lt;/p&gt;

&lt;p&gt;Suppose you would like to change the OAuth provider being used. In that case, you will need to update the &lt;code&gt;OAUTH2_PROXY_PROVIDER&lt;/code&gt;, &lt;code&gt;OAUTH2_PROXY_CLIENT_ID&lt;/code&gt;, and the &lt;code&gt;OAUTH2_PROXY_CLIENT_SECRET&lt;/code&gt; variables on the app &lt;em&gt;after&lt;/em&gt; reading &lt;a href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider" rel="noopener noreferrer"&gt;the &lt;code&gt;oauth2-proxy&lt;/code&gt; documentation&lt;/a&gt; for the associated provider type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Restricting to a GitHub Organization
&lt;/h2&gt;

&lt;p&gt;Cool, our documentation now has OAuth in front of it, meaning people have to log into their GitHub accounts before viewing it. That's great, but we said this was for internal company documentation; we don't want just &lt;em&gt;any&lt;/em&gt; GitHub user seeing our documentation.&lt;/p&gt;

&lt;p&gt;Reading through &lt;a href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider#github-auth-provider" rel="noopener noreferrer"&gt;the &lt;code&gt;oauth2-proxy&lt;/code&gt; GitHub Provider documentation&lt;/a&gt;, we can see that the provider can implement more strict access controls at the organization, team, repository, token, or user level. Let's restrict this to a single team by adding a configuration argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku config:set &lt;span class="nv"&gt;OAUTH2_PROXY_ARGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-github-org='my-cool-org'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And update the &lt;code&gt;Procfile&lt;/code&gt; to read the &lt;code&gt;OAUTH2_PROXY_ARGS&lt;/code&gt; configuration variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;web: /app/bin/start_with_oauth2_proxy.sh &lt;span class="nv"&gt;$OAUTH2_PROXY_ARGS&lt;/span&gt; npm run serve &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that changing who can access the documentation at different abstraction levels boils down to just updating the &lt;code&gt;OAUTH2_PROXY_ARGS&lt;/code&gt; configuration variable, which can be done via the &lt;code&gt;heroku&lt;/code&gt; CLI or dashboard under the app settings. Tinkering with this config var is left as a final exercise to the user.&lt;/p&gt;




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

&lt;p&gt;With this setup, engineering (and non-engineering teams too!) can write easy-to-manage, easier-to-view documentation sites powered by Docusaurus while ensuring that only appropriate individuals can view them. This detail is usually critical for internal company documentation. Much more configuration and OAuth settings can be included by reading through &lt;a href="https://oauth2-proxy.github.io/oauth2-proxy/docs/" rel="noopener noreferrer"&gt;the &lt;code&gt;oauth2-proxy&lt;/code&gt; documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading this post! I hope it helps you and your teams as much as figuring out how to make this work helped mine.&lt;/p&gt;

</description>
      <category>heroku</category>
      <category>tutorial</category>
      <category>docusaurus</category>
      <category>oauth</category>
    </item>
    <item>
      <title>Managing Django Media &amp; Static Files on Heroku with Bucketeer</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Thu, 13 Jan 2022 18:22:54 +0000</pubDate>
      <link>https://dev.to/heroku/properly-managing-django-media-static-files-on-heroku-o2l</link>
      <guid>https://dev.to/heroku/properly-managing-django-media-static-files-on-heroku-o2l</guid>
      <description>&lt;p&gt;This article will walk through how we correctly persist static &amp;amp; media files for a Django application hosted on Heroku. As a bonus, it will also explain how we can satisfy the additional constraint of specifying private versus public media files based on model definitions.&lt;/p&gt;

&lt;p&gt;Before I begin, this post extends from &lt;a href="https://testdriven.io/blog/storing-django-static-and-media-files-on-amazon-s3/" rel="noopener noreferrer"&gt;this TestDriven.io article&lt;/a&gt; that was written awhile back. I frequent it often when setting up my projects, and have built some extra functionality on top of it over the years. I decided to create a more focused post that references Heroku &amp;amp; Bucketeer with these extra features after helping an individual on StackOverflow.&lt;/p&gt;


&lt;div class="ltag__stackexchange--container"&gt;
  &lt;div class="ltag__stackexchange--title-container"&gt;
    
      &lt;div class="ltag__stackexchange--title"&gt;
        &lt;div class="ltag__stackexchange--header"&gt;
          &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fstackoverflow-logo-b42691ae545e4810b105ee957979a853a696085e67e43ee14c5699cf3e890fb4.svg" alt=""&gt;
          &lt;a href="https://stackoverflow.com/questions/70481782/how-to-add-image-for-image-field-for-a-model-instance-in-django-admin-panel-on-h/70483913#70483913" rel="noopener noreferrer"&gt;
            &lt;span class="title-flare"&gt;answer&lt;/span&gt; re: How to add image for image field for a model instance in django admin panel on heroku?
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="ltag__stackexchange--post-metadata"&gt;
          &lt;span&gt;Dec 26 '21&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;a class="ltag__stackexchange--score-container" href="https://stackoverflow.com/questions/70481782/how-to-add-image-for-image-field-for-a-model-instance-in-django-admin-panel-on-h/70483913#70483913" rel="noopener noreferrer"&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fstackexchange-arrow-up-eff2e2849e67d156181d258e38802c0b57fa011f74164a7f97675ca3b6ab756b.svg" alt=""&gt;
        &lt;div class="ltag__stackexchange--score-number"&gt;
          0
        &lt;/div&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fstackexchange-arrow-down-4349fac0dd932d284fab7e4dd9846f19a3710558efde0d2dfd05897f3eeb9aba.svg" alt=""&gt;
      &lt;/a&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--body"&gt;
    
&lt;blockquote&gt;
&lt;p&gt;I think it's because I turn off a PC, where I took these images&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This probably is not it, because Heroku doesn't have access to the files on your computer.&lt;/p&gt;

&lt;p&gt;When you upload a file to the Django admin, it looks at the &lt;code&gt;DEFAULT_FILE_STORAGE&lt;/code&gt; settings configuration to determine how to…&lt;/p&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--btn--container"&gt;
    &lt;a href="https://stackoverflow.com/questions/70481782/how-to-add-image-for-image-field-for-a-model-instance-in-django-admin-panel-on-h/70483913#70483913" class="ltag__stackexchange--btn" rel="noopener noreferrer"&gt;Open Full Answer&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;So without further ado, let's first dive into what static &amp;amp; media files are and how Heroku dynos manage their filesystem? &lt;/p&gt;

&lt;h2&gt;
  
  
  What are Media &amp;amp; Static Files
&lt;/h2&gt;

&lt;p&gt;If you are working with a &lt;a href="http://djangoproject.com/" rel="noopener noreferrer"&gt;Django&lt;/a&gt; project, then you inevitably have all of your Python application code written around a bunch of &lt;code&gt;.py&lt;/code&gt; files. These are the code paths of your application, and the end-user - hopefully - never actually sees these files or their contents.&lt;/p&gt;

&lt;p&gt;Outside of these business-logic files, it is common to serve users directly from your server's file system. For these &lt;strong&gt;static files&lt;/strong&gt;, Django doesn't need to run any code for them; the framework looks up the file and returns the contents for the requesting user to view.&lt;/p&gt;

&lt;p&gt;Some examples of static files include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non-templated HTML&lt;/li&gt;
&lt;li&gt;CSS &amp;amp; JavaScript files to make your page look nice&lt;/li&gt;
&lt;li&gt;User profile pictures&lt;/li&gt;
&lt;li&gt;Generated PDFs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Media files&lt;/strong&gt; in Django are a particular variant of static files. Media files are read from the server's file system as well. Unlike static files, though, they are usually generated files uploaded by users or generated by your application and are associated with a model's &lt;a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#filefield" rel="noopener noreferrer"&gt;&lt;code&gt;FileField&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.ImageField" rel="noopener noreferrer"&gt;&lt;code&gt;ImageField&lt;/code&gt;&lt;/a&gt;. In the examples above, user profile pictures and generated PDFs are typical examples of media files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Django with Media &amp;amp; Static Files
&lt;/h3&gt;

&lt;p&gt;When a new media file is uploaded to a Django web application, the framework looks at the &lt;code&gt;DEFAULT_FILE_STORAGE&lt;/code&gt; settings configuration to determine how to store that file. &lt;a href="https://docs.djangoproject.com/en/4.0/ref/settings/#default-file-storage" rel="noopener noreferrer"&gt;By default&lt;/a&gt;, it uses the &lt;a href="https://docs.djangoproject.com/en/4.0/ref/files/storage/#django.core.files.storage.FileSystemStorage" rel="noopener noreferrer"&gt;&lt;code&gt;django.core.files.storage.FileSystemStorage&lt;/code&gt; class&lt;/a&gt;, which is what most projects start off as having configured. This implementation looks at the &lt;a href="https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-MEDIA_ROOT" rel="noopener noreferrer"&gt;&lt;code&gt;MEDIA_ROOT&lt;/code&gt; configuration&lt;/a&gt; that is defined in the &lt;code&gt;settings.py&lt;/code&gt; file and copies the uploaded file contents to &lt;a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.FileField.upload_to" rel="noopener noreferrer"&gt;a deterministically-created file path&lt;/a&gt; under that given &lt;code&gt;MEDIA_ROOT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, if the &lt;code&gt;MEDIA_ROOT&lt;/code&gt; is set as &lt;code&gt;/var/www/media&lt;/code&gt;, all uploaded files will be copied and written to a location under &lt;code&gt;/var/www/media/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heroku with Media &amp;amp; Static Files
&lt;/h3&gt;

&lt;p&gt;Storing these static files on your server's disk file system is okay until you start to work with a containerization platform such as &lt;a href="https://www.heroku.com/home" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;. To explain why this is the case, it helps to take a step back.&lt;/p&gt;

&lt;p&gt;When downloading files on your personal computer, it's okay that these get written to the file system - usually under &lt;code&gt;~/Downloads&lt;/code&gt; or somewhere similar. This download is because you &lt;em&gt;expect&lt;/em&gt; your computer's file system to persist across restarts and shutdowns; if you download a file and restart your computer, that downloaded file should still be there once the laptop is finished restarting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://heroku.com/home" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt; uses &lt;a href="https://www.docker.com/resources/what-container" rel="noopener noreferrer"&gt;containerization&lt;/a&gt; to execute customer workloads. One fact of this environment is that the associated file systems do not persist across restarts and reschedules. &lt;a href="https://www.heroku.com/dynos" rel="noopener noreferrer"&gt;Heroku dynos&lt;/a&gt; are ephemeral, and they can be destroyed, restarted, and moved without any warning, which &lt;a href="https://devcenter.heroku.com/articles/dynos#ephemeral-filesystem" rel="noopener noreferrer"&gt;replaces the associated filesystem&lt;/a&gt;. This situation means that any uploaded files referenced by &lt;code&gt;FileField's and&lt;/code&gt;ImageField's are just deleted without a trace every time the dyno is restarted, moved, or scaled.&lt;/p&gt;




&lt;h2&gt;
  
  
  Complete Example Codebase
&lt;/h2&gt;

&lt;p&gt;I will be stepping through the process of configuring the Django application for Heroku &amp;amp; S3-compatible storage, but feel free to reference the repository below for the complete code to browse through.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/dstarner" rel="noopener noreferrer"&gt;
        dstarner
      &lt;/a&gt; / &lt;a href="https://github.com/dstarner/django-heroku-static-file-example" rel="noopener noreferrer"&gt;
        django-heroku-static-file-example
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Used in my blog post of detailing private &amp;amp; public static files for a Heroku-served Django application
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Properly Managing Django Media &amp;amp; Static Files on Heroku Example&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Used in my blog post of detailing private &amp;amp; public static files for a Heroku-served Django application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This does include a $5.00 / month &lt;a href="https://elements.heroku.com/addons/bucketeer" rel="nofollow noopener noreferrer"&gt;Bucketeer add-on&lt;/a&gt; as a part of the one-click deployment.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://heroku.com/deploy?template=https://github.com/dstarner/django-heroku-static-file-example" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/dc2056acd0e6ff421bfc2b129417f4f832d626c61d1c083221211d8503a429f7/68747470733a2f2f7777772e6865726f6b7563646e2e636f6d2f6465706c6f792f627574746f6e2e737667" alt="Deploy"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/dstarner/django-heroku-static-file-example" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Bootstrapping Django on Heroku
&lt;/h2&gt;

&lt;p&gt;This tutorial aims to help you retrofit an existing Django project with S3-compatible storage, but I'll quickly go through the steps I used to set up the example Django application. It may help those new to Django &amp;amp; Heroku or those who encounter bugs following the rest of the setup process.&lt;/p&gt;

&lt;p&gt;You can view the tagged project before the storage change &lt;a href="https://github.com/dstarner/django-heroku-static-file-example/commit/299bbe2403e3c35b4cd905aa61eee974ccdb9558" rel="noopener noreferrer"&gt;at commit &lt;code&gt;299bbe2&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bootstrapped a Django project &lt;code&gt;example&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Uses &lt;a href="https://python-poetry.org/" rel="noopener noreferrer"&gt;&lt;code&gt;poetry&lt;/code&gt;&lt;/a&gt; for dependency management&lt;/li&gt;
&lt;li&gt;All of the Django code is under the &lt;code&gt;example&lt;/code&gt; package, and the &lt;code&gt;manage.py&lt;/code&gt; file is in the root. I've always found this structure cleaner than the &lt;a href="https://docs.djangoproject.com/en/4.0/ref/applications/" rel="noopener noreferrer"&gt;Django apps&lt;/a&gt; defined in the project root.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Configured the project for Heroku

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pypi.org/project/django-heroku/" rel="noopener noreferrer"&gt;&lt;code&gt;django-heroku&lt;/code&gt; package&lt;/a&gt; to automatically configure &lt;code&gt;ALLOWED_HOSTS&lt;/code&gt;, &lt;code&gt;DATABASE_URL&lt;/code&gt;, and more. This reduces the headache of deploying Django on Heroku considerably&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://devcenter.heroku.com/articles/procfile" rel="noopener noreferrer"&gt;&lt;code&gt;Procfile&lt;/code&gt;&lt;/a&gt; that runs a &lt;a href="https://gunicorn.org/" rel="noopener noreferrer"&gt;&lt;code&gt;gunicorn&lt;/code&gt; process&lt;/a&gt; for managing the WSGI application&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;app.json&lt;/code&gt; is defined with some fundamental configuration values and resources defined for the project to work&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;release&lt;/code&gt; process definition in the &lt;code&gt;Procfile&lt;/code&gt; and an associated &lt;code&gt;scripts/release.sh&lt;/code&gt; script that runs staticfile collection and database migrations&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introducing Heroku's Bucketeer Add-On
&lt;/h2&gt;

&lt;p&gt;Before we can start managing static and media files, the Django application needs a persistent place to store the files. Again, we can look to &lt;a href="https://elements.heroku.com/addons" rel="noopener noreferrer"&gt;Heroku's extensive list of Add-Ons&lt;/a&gt; for s3-compatible storage. Ours of choice will be one called &lt;strong&gt;Bucketeer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Heroku's &lt;a href="https://elements.heroku.com/addons/bucketeer" rel="noopener noreferrer"&gt;Bucketeer add-on&lt;/a&gt; provides an &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;AWS S3 storage&lt;/a&gt; bucket to upload and download files for our application. The Django application will use this configured bucket to store files uploaded by the server and download them from the S3 when a user requests the files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you'd like to learn more about AWS S3, the widely-popular data storage solution that Bucketeer is built upon, you can read the &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html" rel="noopener noreferrer"&gt;S3 user documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;It is worth mentioning that the base plan for Bucketeer - &lt;code&gt;Hobbyist&lt;/code&gt; - is  $5 per month.&lt;/strong&gt; If you plan on spinning up the one-click example posted above, it should only cost a few cents if you &lt;a href="https://help.heroku.com/LGKL6LTN/how-do-i-delete-destroy-a-heroku-application" rel="noopener noreferrer"&gt;proactively destroy the application&lt;/a&gt; when you are done using it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Including the Bucketeer Add-On
&lt;/h3&gt;

&lt;p&gt;To include the &lt;a href="https://elements.heroku.com/addons/bucketeer" rel="noopener noreferrer"&gt;Bucketeer add-on&lt;/a&gt; in our application, we can configure it through the Heroku CLI, web dashboard, or via the project's &lt;code&gt;app.json&lt;/code&gt; file. We will use the third method of including the add-on in an &lt;code&gt;app.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;If the project does not have one already, we can create the basic structure listed below, with the critical part being the addition of the &lt;code&gt;"add-ons"&lt;/code&gt; configuration. This array defines the &lt;code&gt;"bucketeer:hobbyist"&lt;/code&gt; resource that our application will use, and Heroku will install the add-on into our application if it does not already exist. We also include the &lt;code&gt;" as"&lt;/code&gt; keyword, which will preface the associated configuration variables with the term &lt;code&gt;BUCKETEER&lt;/code&gt;. This prefacing is helpful to keep the generated configuration value names deterministic because, by default, Heroku will generate the prefix as a random color.&lt;/p&gt;

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

&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... rest above&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addons&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;// ...other addons...&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plan&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="s2"&gt;bucketeer:hobbyist&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="s2"&gt;as&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="s2"&gt;BUCKETEER&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;With the required resources being defined, we can start integrating with our storage add-on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Our Storage Solution
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://django-storages.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;&lt;code&gt;django-storages&lt;/code&gt; package&lt;/a&gt; is a collection of custom, reuseable storage backends for Django. It aids immensely in saving static and media files to different cloud &amp;amp; storage provider options. &lt;a href="https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html" rel="noopener noreferrer"&gt;One of the supported storage providers is S3&lt;/a&gt;, which our Bucketeer add-on is built on. We will leverage the S3 &lt;code&gt;django-storages&lt;/code&gt; backend to handle different file types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing &lt;code&gt;django-storages&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Begin by installing the &lt;code&gt;django-storages&lt;/code&gt; package and the related &lt;a href="https://aws.amazon.com/sdk-for-python/" rel="noopener noreferrer"&gt;&lt;code&gt;boto3&lt;/code&gt; package&lt;/a&gt; used to interface with AWS's S3. We will also lock our dependencies to ensure &lt;code&gt;poetry&lt;/code&gt; and our Heroku deployment continue to work as expected.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;

poetry add django-storages boto3 &amp;amp;&amp;amp; poetry lock


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

&lt;/div&gt;

&lt;p&gt;Then, just like most Django-related packages, &lt;code&gt;django-storages&lt;/code&gt; will need to be added to the project's &lt;code&gt;INSTALLED_APPS&lt;/code&gt; in the projects &lt;code&gt;settings.py&lt;/code&gt; file. This will allow Django to load the appropriate code flows as the application starts up.&lt;/p&gt;

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

&lt;span class="c1"&gt;# example/config/settings.py
&lt;/span&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ... django.X.Y apps above
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;storages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ... custom project apps below
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Implementing Static, Public &amp;amp; Private Storage Backends
&lt;/h3&gt;

&lt;p&gt;We will return to the &lt;code&gt;settings.py&lt;/code&gt; file later to configure the usage of &lt;code&gt;django-storages&lt;/code&gt;, but before that can be done, we will implement three custom &lt;a href="https://docs.djangoproject.com/en/4.0/howto/custom-file-storage/" rel="noopener noreferrer"&gt;storage backends&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A storage backend for static files - CSS, Javascript, and publicly accessible images - that will be stored in version control - aka &lt;code&gt;git&lt;/code&gt; - and shipped with the application&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;public&lt;/strong&gt; storage backend for dynamic media files that are not stored in version control, such as uploaded files and attachments&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;private&lt;/strong&gt; storage backend for dynamic media files that are not stored in the version control that require extra access to be viewed, such as per-user reports and potentially profile images. Files managed by this backend require an access key and will block access to those without a valid key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can extend from &lt;code&gt;django-storages&lt;/code&gt; 's &lt;code&gt;S3Boto3Storage&lt;/code&gt; storage backend to create these. The following code can be directly "copy and paste "'d into your project. The different &lt;code&gt;settings&lt;/code&gt; attributes read in the module will be written shortly, so &lt;strong&gt;do not expect this code to work if you import it right now&lt;/strong&gt;. &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# FILE: example/utils/storage_backends.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;storages.backends.s3boto3&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;S3Boto3Storage&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StaticStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S3Boto3Storage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Used to manage static files for the web server&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STATIC_LOCATION&lt;/span&gt;
    &lt;span class="n"&gt;default_acl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STATIC_DEFAULT_ACL&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PublicMediaStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S3Boto3Storage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Used to store &amp;amp; serve dynamic media files with no access expiration&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PUBLIC_MEDIA_LOCATION&lt;/span&gt;
    &lt;span class="n"&gt;default_acl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PUBLIC_MEDIA_DEFAULT_ACL&lt;/span&gt;
    &lt;span class="n"&gt;file_overwrite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PrivateMediaStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S3Boto3Storage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Used to store &amp;amp; serve dynamic media files using access keys
    and short-lived expirations to ensure more privacy control
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PRIVATE_MEDIA_LOCATION&lt;/span&gt;
    &lt;span class="n"&gt;default_acl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PRIVATE_MEDIA_DEFAULT_ACL&lt;/span&gt;
    &lt;span class="n"&gt;file_overwrite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;custom_domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The attributes listed in each storage backend class perform the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;location&lt;/code&gt;: This dictates the parent directory used in the S3 bucket for associated files. This is concatenated with the generated path provided by a &lt;code&gt;FileField&lt;/code&gt; or &lt;code&gt;ImageField&lt;/code&gt; 's &lt;a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.FileField.upload_to" rel="noopener noreferrer"&gt;&lt;code&gt;upload_to&lt;/code&gt; method&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;default_acl&lt;/code&gt;: This dictates the access policy required for reading the files. This dictates the storage backend's access control through values of &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;public-read&lt;/code&gt;, and &lt;code&gt;private&lt;/code&gt;. &lt;code&gt;django-storages&lt;/code&gt; and the &lt;code&gt;S3Boto3Storage&lt;/code&gt; parent class with translate these into object policies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;file_overwrite&lt;/code&gt;: In most cases, it's better not to overwrite existing files if we update a specific path. With this set to &lt;code&gt;False&lt;/code&gt;, a unique suffix will be appended to the path to prevent naming collisions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;custom_domain&lt;/code&gt;: Disabled here, but you can enable it if you want to use &lt;a href="https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#cloudfront" rel="noopener noreferrer"&gt;AWS's CloudFront and &lt;code&gt;django-storage&lt;/code&gt; to serve from it&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configure Settings to Use the Storage Backends
&lt;/h3&gt;

&lt;p&gt;With our storage backends defined, we can configure them to be used in different situations via the &lt;code&gt;settings.py&lt;/code&gt; file. However, it is challenging to use S3 and these different cloud storage backends while in development, and I've always been a proponent of keeping all resources and files "local" to the development machine, so we will create a logic path that will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the local filesystem to store static and media files for convenience. The Django server will be responsible for serving these files directly.&lt;/li&gt;
&lt;li&gt;Use the custom S3 storage backends when an environment variable is enabled. We will use the &lt;code&gt;S3_ENABLED&lt;/code&gt; variable to control this, enabling it in our Heroku configuration variables.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, we will assume that you have a relatively vanilla &lt;code&gt;settings.py&lt;/code&gt; file concerning the static- &amp;amp; media-related variables. For reference, a new project should have a block that looks similar to the following:&lt;/p&gt;

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

&lt;span class="c1"&gt;# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
&lt;/span&gt;
&lt;span class="n"&gt;STATIC_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;static/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;STATIC_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;collected-static&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We will design a slightly advanced control flow that will seamlessly handle the two cases defined above. In addition, it will provide enough control to override each part of the configuration as needed.&lt;/p&gt;

&lt;p&gt;Since there are already default values for the static file usage, we can add default values for media file usage. These will be used when serving files locally from the server while in development mode.&lt;/p&gt;

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

&lt;span class="n"&gt;STATIC_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/static/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;STATIC_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;collected-static&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;MEDIA_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/media/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;MEDIA_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;collected-media&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;To begin the process of including S3, let's create the controls to manage if we should serve static &amp;amp; media files from the local server or through the S3 storage backend. We will create three variables&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;S3_ENABLED&lt;/code&gt;: controls whether media &amp;amp; static files should use S3 storage by default&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LOCAL_SERVE_MEDIA_FILES&lt;/code&gt;: controls whether media files should use S3 storage. Defaults to the negated &lt;code&gt;S3_ENABLED&lt;/code&gt; value&lt;/li&gt;
&lt;li&gt; &lt;code&gt;LOCAL_SERVE_STATIC_FILES&lt;/code&gt;: controls whether static files should use S3 storage. Defaults to the negated &lt;code&gt;S3_ENABLED&lt;/code&gt; value&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decouple&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;  &lt;span class="c1"&gt;# import explained below
&lt;/span&gt;
&lt;span class="c1"&gt;# ...STATIC and MEDIA settings here...
&lt;/span&gt;
&lt;span class="c1"&gt;# The following configs determine if files get served from the server or an S3 storage
&lt;/span&gt;&lt;span class="n"&gt;S3_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S3_ENABLED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;LOCAL_SERVE_MEDIA_FILES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LOCAL_SERVE_MEDIA_FILES&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;S3_ENABLED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;LOCAL_SERVE_STATIC_FILES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LOCAL_SERVE_STATIC_FILES&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;S3_ENABLED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;LOCAL_SERVE_MEDIA_FILES&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;LOCAL_SERVE_STATIC_FILES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;S3_ENABLED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S3_ENABLED must be true if either media or static files are not served locally&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;In the example above, we are using the &lt;a href="https://pypi.org/project/python-decouple/" rel="noopener noreferrer"&gt;&lt;code&gt;python-decouple&lt;/code&gt; package&lt;/a&gt; to make it easier to read and cast environment variables to Python variables. I highly recommend this package when working with &lt;code&gt;settings.py&lt;/code&gt; configurations. We also include a value check to ensure consistency across these three variables. If all three variables are defined in the environment but conflict with one another, the program will throw an error.&lt;/p&gt;

&lt;p&gt;We can now start configuring the different configuration variables required by our file storage backends based on those control variables' value(s). We begin by including some S3 configurations required whether we are serving static, media, or both types of files.&lt;/p&gt;

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

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;S3_ENABLED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BUCKETEER_AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BUCKETEER_AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;AWS_STORAGE_BUCKET_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BUCKETEER_BUCKET_NAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;AWS_S3_REGION_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BUCKETEER_AWS_REGION&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;AWS_DEFAULT_ACL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;AWS_S3_SIGNATURE_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S3_SIGNATURE_VERSION&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3v4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;AWS_S3_ENDPOINT_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;AWS_STORAGE_BUCKET_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.s3.amazonaws.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;AWS_S3_OBJECT_PARAMETERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CacheControl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;max-age=86400&lt;/span&gt;&lt;span class="sh"&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 above defines some of the variables required by the &lt;a href="https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html" rel="noopener noreferrer"&gt;&lt;code&gt;django-storages&lt;/code&gt; S3 backend&lt;/a&gt; and sets the values to environment configurations that are provided by the Bucketeer add-on. As previously mentioned, all of the add-on environment variables are prefixed with &lt;code&gt;BUCKETEER_&lt;/code&gt;. The &lt;code&gt;S3_SIGNATURE_VERSION&lt;/code&gt; environment variable is not required and &lt;em&gt;most likely&lt;/em&gt; does not need to be included.&lt;/p&gt;

&lt;p&gt;With the S3 configuration together, we can reference the &lt;code&gt;LOCAL_SERVE_MEDIA_FILES&lt;/code&gt; and &lt;code&gt;LOCAL_SERVE_STATIC_FILES&lt;/code&gt; control variables to override the default static and media file settings if they are desired to be served via S3.&lt;/p&gt;

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

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;LOCAL_SERVE_STATIC_FILES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;STATIC_DEFAULT_ACL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;public-read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;STATIC_LOCATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;static&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;STATIC_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;AWS_S3_ENDPOINT_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;STATIC_LOCATION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;STATICFILES_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;example.utils.storage_backends.StaticStorage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Notice the last line where &lt;code&gt;STATICFILES_STORAGE&lt;/code&gt; is set to the custom Backend we created. That ensures it follows the location &amp;amp; ACL (Access Control List) policies that we configured initially. With this configuration, all static files will be placed under &lt;code&gt;/static/&lt;/code&gt; in the bucket, but feel free to update &lt;code&gt;STATIC_LOCATION&lt;/code&gt; if desired.&lt;/p&gt;

&lt;p&gt;We can configure a very similar situation for media files.&lt;/p&gt;

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

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;LOCAL_SERVE_MEDIA_FILES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;PUBLIC_MEDIA_DEFAULT_ACL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;public-read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;PUBLIC_MEDIA_LOCATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;media/public&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="n"&gt;MEDIA_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;AWS_S3_ENDPOINT_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PUBLIC_MEDIA_LOCATION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;DEFAULT_FILE_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;example.utils.storage_backends.PublicMediaStorage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="n"&gt;PRIVATE_MEDIA_DEFAULT_ACL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;private&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;PRIVATE_MEDIA_LOCATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;media/private&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;PRIVATE_FILE_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;example.utils.storage_backends.PrivateMediaStorage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The big difference here is that we have configured &lt;em&gt;two&lt;/em&gt; different storage backends for media files; one for publicly accessible objects and one for objects that require an access token. When the file is requested, this token will be generated internally by &lt;code&gt;django-storages&lt;/code&gt; so you do not have to worry about anonymous public access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local Development Serving
&lt;/h3&gt;

&lt;p&gt;Since we will have &lt;code&gt;S3_ENABLED&lt;/code&gt; set to &lt;code&gt;False&lt;/code&gt; in our local development environment, it will serve static and media files locally through the Django server instead of from S3. We will need to configure the URL routing to handle this scenario. We can configure our &lt;code&gt;urls.py&lt;/code&gt; file to serve the appropriate files like so:&lt;/p&gt;

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

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf.urls.static&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;static&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;


&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LOCAL_SERVE_STATIC_FILES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STATIC_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STATIC_ROOT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LOCAL_SERVE_MEDIA_FILES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEDIA_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEDIA_ROOT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This will locally serve the static or media files based on the values of the &lt;code&gt;LOCAL_SERVE_STATIC_FILES&lt;/code&gt; and &lt;code&gt;LOCAL_SERVE_MEDIA_FILES&lt;/code&gt; settings variables we defined.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enabling S3 Storage
&lt;/h3&gt;

&lt;p&gt;We can enable these storages and our add-on in the &lt;code&gt;app.json&lt;/code&gt; file to start using these storage backends. This will effectively disable &lt;code&gt;LOCAL_SERVE_STATIC_FILES&lt;/code&gt; and &lt;code&gt;LOCAL_SERVE_MEDIA_FILES&lt;/code&gt; to start serving both via S3 when deployed to Heroku.&lt;/p&gt;

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

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...rest of configs...&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;env&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;// ...rest of envs...&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;S3_ENABLED&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&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="s2"&gt;Enable to upload &amp;amp; serve static and media files from S3&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="s2"&gt;value&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="s2"&gt;True&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Using the Private Storage
&lt;/h3&gt;

&lt;p&gt;By default, Django will use the &lt;code&gt;PublicMediaStorage&lt;/code&gt; class for uploading media files, meaning the contents will be publicly accessible to anyone with the link. However, a model can utilize the &lt;code&gt;PrivateMediaStorage&lt;/code&gt; backend when desired, which will create short-lived access tokens that prevent the public from viewing the associated object.&lt;/p&gt;

&lt;p&gt;The below is an example of using public and private media files on the same model.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;example.utils.storage_backends&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PrivateMediaStorage&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Organization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A sample Organization model with public and private file field usage
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;logo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;A publicly accessible company logo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;expense_report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FileField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;The private expense report requires a short-lived access token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;PrivateMediaStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# will create private files
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;




&lt;p&gt;You can see the code for this complete example &lt;a href="https://github.com/dstarner/django-heroku-static-file-example/commit/265becc025cd41ebe1de6cb489150b7f6b110f23" rel="noopener noreferrer"&gt;at commit &lt;code&gt;265becc&lt;/code&gt;&lt;/a&gt;. This configuration will allow your project to scale efficiently using &lt;a href="https://www.djangoproject.com/" rel="noopener noreferrer"&gt;Django&lt;/a&gt; on &lt;a href="https://www.heroku.com/home" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt; using &lt;a href="https://elements.heroku.com/addons/bucketeer" rel="noopener noreferrer"&gt;Bucketeer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In a future post, we will discuss how to upload and set these files using vanilla Django &amp;amp; &lt;a href="https://www.django-rest-framework.org/" rel="noopener noreferrer"&gt;Django REST Framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As always, if you find any bugs, issues, or unclear explanations, please reach out to me so I can improve the tutorial &amp;amp; experience for future readers.&lt;/p&gt;

&lt;p&gt;Take care everyone&lt;/p&gt;

</description>
      <category>django</category>
      <category>heroku</category>
      <category>aws</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building Dynamic Breadcrumbs in NextJS</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Wed, 05 Jan 2022 01:25:37 +0000</pubDate>
      <link>https://dev.to/dan_starner/building-dynamic-breadcrumbs-in-nextjs-17oa</link>
      <guid>https://dev.to/dan_starner/building-dynamic-breadcrumbs-in-nextjs-17oa</guid>
      <description>&lt;p&gt;Breadcrumbs are a website navigation tool that allows users to see their current page's "stack" of how it is nested under any parent pages. Users can then jump back to a parent page by clicking the associated breadcrumb link. These &lt;em&gt;"Crumbs"&lt;/em&gt; increase the User Experience of the application, making it easier for the users to navigate nested pages efficiently and effectively.&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%2Fnt8zfqwqign5whs1y9zb.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%2Fnt8zfqwqign5whs1y9zb.png" alt="Example Breadcrumbs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Breadcrumbs are popular enough while building a web dashboard or application that you may have considered adding them. Generating these breadcrumb links efficiently and with the appropriate context is key to an improved user experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's build an intelligent &lt;code&gt;NextBreadcrumbs&lt;/code&gt; React component that will parse the current route and create a dynamic breadcrumbs display that can handle both static &amp;amp; dynamic routes efficiently.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My projects usually revolve around &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Nextjs&lt;/a&gt; and &lt;a href="https://mui.com/" rel="noopener noreferrer"&gt;MUI&lt;/a&gt; (formerly Material-UI), so that is the angle that I am going to approach this problem from, although the solution should work for &lt;em&gt;any&lt;/em&gt; Nextjs-related application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Route Breadcrumbs
&lt;/h2&gt;

&lt;p&gt;To begin, our &lt;code&gt;NextBreadcrumbs&lt;/code&gt; component will only handle static routes, meaning that our project has only static pages defined in the &lt;code&gt;pages&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;The following are examples of static routes because they do not contain &lt;code&gt;['s and&lt;/code&gt;] 's in the route names, meaning the directory structure lines up 1:1 precisely with the expected URLs that they serve.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pages/index.js&lt;/code&gt; --&amp;gt; &lt;code&gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages/about.js&lt;/code&gt; --&amp;gt; &lt;code&gt;/about&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages/my/super/nested/route.js&lt;/code&gt; --&amp;gt; &lt;code&gt;/my/super/nested/route&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution will be extended to handle dynamic routes later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the Basic Component
&lt;/h3&gt;

&lt;p&gt;We can start with the fundamental component that uses the &lt;a href="https://mui.com/components/breadcrumbs/" rel="noopener noreferrer"&gt;MUI &lt;code&gt;Breadcrumbs&lt;/code&gt; component&lt;/a&gt; as a baseline.&lt;/p&gt;

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

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Breadcrumbs&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;@mui/material/Breadcrumbs&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;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;NextBreadcrumbs&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Breadcrumbs&lt;/span&gt; &lt;span class="nx"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;breadcrumb&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The above creates the basic structure of the &lt;code&gt;NextBreadcrumbs&lt;/code&gt; React component, imports the correct dependencies, and renders an empty &lt;a href="https://mui.com/components/breadcrumbs/" rel="noopener noreferrer"&gt;&lt;code&gt;Breadcrumbs&lt;/code&gt; MUI component&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We can then add in the &lt;a href="https://nextjs.org/docs/api-reference/next/router" rel="noopener noreferrer"&gt;&lt;code&gt;next/router&lt;/code&gt;&lt;/a&gt; hooks, which will allow us to build the breadcrumbs from the current route.&lt;/p&gt;

&lt;p&gt;We also create a &lt;code&gt;Crumb&lt;/code&gt; component that will be used to render each link. This is a pretty dumb component for now, except that it will render basic text instead of a link for the last breadcrumb.&lt;/p&gt;

&lt;p&gt;In a situation like &lt;code&gt;/settings/notifications&lt;/code&gt;, it would render as the following:&lt;/p&gt;

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

Home (/ link) &amp;gt; Settings (/settings link) &amp;gt; Notifications (no link)


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

&lt;/div&gt;

&lt;p&gt;The user is already on the last breadcrumb's page, so there is no need to link out to the same page. All the other crumbs are rendered as links to be clicked.&lt;/p&gt;

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

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Breadcrumbs&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;@mui/material/Breadcrumbs&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;Link&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;@mui/material/Link&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;Typography&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;@mui/material/Typography&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;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;NextBreadcrumbs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Gives us ability to load the current route details&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Breadcrumbs&lt;/span&gt; &lt;span class="nx"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;breadcrumb&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;// Each individual "crumb" in the breadcrumbs list&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Crumb&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The last crumb is rendered as normal text since we are already on the page&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;last&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text.primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&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;/Typography&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// All other crumbs will be rendered as links that can be visited &lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;underline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hover&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inherit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&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;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We can then dive back into the &lt;code&gt;NextBreadcrumbs&lt;/code&gt; component to generate the breadcrumbs from the route with this layout. &lt;strong&gt;Some existing code will start to be omitted to keep the code pieces smaller. The full example is shown below.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will generate a list of breadcrumb objects that contain the information to be rendered by each &lt;code&gt;Crumb&lt;/code&gt; element. Each breadcrumb will be created by parsing the &lt;a href="https://nextjs.org/docs/api-reference/next/router#router-object" rel="noopener noreferrer"&gt;Nextjs router's &lt;code&gt;asPath&lt;/code&gt;&lt;/a&gt; property, which is a string containing the route as shown in the browser URL bar. &lt;/p&gt;

&lt;p&gt;We will strip any query parameters, such as &lt;code&gt;?query=value&lt;/code&gt;, from the URL to simplify the breadcrumb creation process.&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;NextBreadcrumbs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Gives us ability to load the current route details&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateBreadcrumbs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Remove any query parameters, as those aren't included in breadcrumbs&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;asPathWithoutQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Break down the path between "/"s, removing empty entities&lt;/span&gt;
    &lt;span class="c1"&gt;// Ex:"/my/nested/path" --&amp;gt; ["my", "nested", "path"]&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;asPathWithoutQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="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;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Iterate over the list of nested route parts and build&lt;/span&gt;
    &lt;span class="c1"&gt;// a "crumb" object for each one.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crumblist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&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;subpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&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;// We can get the partial nested route for the crumb&lt;/span&gt;
      &lt;span class="c1"&gt;// by joining together the path parts up to this point.&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&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="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="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;// The title will just be the route string for now&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="nx"&gt;subpath&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;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; 
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// Add in a default "Home" crumb for the top-level&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;href&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="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&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="nx"&gt;crumblist&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Call the function to generate the breadcrumbs list&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;breadcrumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateBreadcrumbs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Breadcrumbs&lt;/span&gt; &lt;span class="nx"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;breadcrumb&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;With this list of breadcrumbs, we can now render them using the &lt;code&gt;Breadcrumbs&lt;/code&gt; and &lt;code&gt;Crumb&lt;/code&gt; components. As previously mentioned, only the &lt;code&gt;return&lt;/code&gt; portion of our component is shown for brevity.&lt;/p&gt;

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

  &lt;span class="c1"&gt;// ...rest of NextBreadcrumbs component above...&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* The old breadcrumb ending with '/&amp;gt;' was converted into this */&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;Breadcrumbs&lt;/span&gt; &lt;span class="nx"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;breadcrumb&lt;/span&gt;&lt;span class="dl"&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="cm"&gt;/*
        Iterate through the crumbs, and render each individually.
        We "mark" the last crumb to not have a link.
      */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;breadcrumbs&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;crumb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&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;Crumb&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;crumb&lt;/span&gt;&lt;span class="p"&gt;}&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;idx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;breadcrumbs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="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;/Breadcrumbs&lt;/span&gt;&lt;span class="err"&gt;&amp;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 should start generating some very basic - but working - breadcrumbs on our site once rendered; &lt;code&gt;/user/settings/notifications&lt;/code&gt; would render as &lt;/p&gt;

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

Home &amp;gt; user &amp;gt; settings &amp;gt; notifications


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Memoizing Generated Breadcrumbs
&lt;/h3&gt;

&lt;p&gt;There is a quick improvement that we can make before going further, though. The breadcrumb list is recreated every time the component re-renders, so we can &lt;a href="https://en.wikipedia.org/wiki/Memoization" rel="noopener noreferrer"&gt;memoize&lt;/a&gt; the crumb list for a given route to save some performance. We can wrap our &lt;code&gt;generateBreadcrumbs&lt;/code&gt; function call in the &lt;a href="https://reactjs.org/docs/hooks-reference.html#usememo" rel="noopener noreferrer"&gt;&lt;code&gt;useMemo&lt;/code&gt; React hook&lt;/a&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// this is the same "generateBreadcrumbs" function, but placed&lt;/span&gt;
  &lt;span class="c1"&gt;// inside a "useMemo" call that is dependent on "router.asPath"&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;breadcrumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateBreadcrumbs&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;asPathWithoutQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="mi"&gt;0&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;asPathNestedRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;asPathWithoutQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="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;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;crumblist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&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;subpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&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;href&lt;/span&gt; &lt;span class="o"&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="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subpath&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;href&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="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&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="nx"&gt;crumblist&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c1"&gt;// ...rest below...&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Improving Breadcrumb Text Display
&lt;/h3&gt;

&lt;p&gt;Before we start incorporating dynamic routes, we can clean this current solution up more by including a nice way to change the text shown for each crumb generated.&lt;/p&gt;

&lt;p&gt;Right now, if we have a path like &lt;code&gt;/user/settings/notifications&lt;/code&gt;, then it will show:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Home &amp;gt; user &amp;gt; settings &amp;gt; notifications


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

&lt;/div&gt;

&lt;p&gt;...which is not very appealing. We can provide a function to the &lt;code&gt;NextBreadcrumbs&lt;/code&gt; component to generate a more user-friendly name for each of these nested route crumbs.&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;_defaultGetDefaultTextGenerator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;NextBreadcrumbs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;getDefaultTextGenerator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;_defaultGetDefaultTextGenerator&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Two things of importance:&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. The addition of getDefaultTextGenerator in the useMemo dependency list&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. getDefaultTextGenerator is now being used for building the text property&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;breadcrumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateBreadcrumbs&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;asPathWithoutQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="mi"&gt;0&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;asPathNestedRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;asPathWithoutQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="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;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;crumblist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&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;subpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&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;href&lt;/span&gt; &lt;span class="o"&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="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getDefaultTextGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;href&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;href&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="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&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="nx"&gt;crumblist&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getDefaultTextGenerator&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="c1"&gt;// ...rest below&lt;/span&gt;



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

&lt;/div&gt;

&lt;p&gt;Then our parent component can have something like the following: to title-ize the subpaths, or maybe even replace them with a new string.&lt;/p&gt;

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

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Assume that `titleize` is written and works appropriately */&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;NextBreadcrumbs&lt;/span&gt; &lt;span class="nx"&gt;getDefaultTextGenerator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;titleize&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="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;

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

&lt;/div&gt;

&lt;p&gt;This implementation would then result in the following breadcrumbs. The complete code example at the bottom has more examples of this.&lt;/p&gt;

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

Home &amp;gt; User &amp;gt; Settings &amp;gt; Notifications


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Nextjs Dynamic Routes
&lt;/h2&gt;

&lt;p&gt;Nextjs's router allows for including &lt;a href="https://nextjs.org/docs/routing/dynamic-routes" rel="noopener noreferrer"&gt;dynamic routes&lt;/a&gt; that uses &lt;a href="https://www.educative.io/edpresso/definition-pattern-matching" rel="noopener noreferrer"&gt;Pattern Matching&lt;/a&gt; to enable the URLs to have slugs, UUIDs, and other dynamic values that will then be passed to your views.&lt;/p&gt;

&lt;p&gt;For example, if your Nextjs application has a page component at &lt;code&gt;pages/post/[post_id].js&lt;/code&gt;, then the routes &lt;code&gt;/post/1&lt;/code&gt; and &lt;code&gt;/post/abc&lt;/code&gt; will match it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For our breadcrumbs component, we would like to show the name of the associated post instead of just its UUID. This means that the component will need to dynamically look up the post data based on the nested URL route path and regenerate the text of the associated crumb.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Right now, if you visit &lt;code&gt;/post/abc&lt;/code&gt;, you would see breadcrumbs that look like&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

post &amp;gt; abc


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

&lt;/div&gt;

&lt;p&gt;but if the post with UUID has a title of &lt;code&gt;My First Post&lt;/code&gt;, then we want to change the breadcrumbs to say&lt;/p&gt;

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

post &amp;gt; My First Post


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

&lt;/div&gt;

&lt;p&gt;Let's dive into how that can happen using &lt;code&gt;async&lt;/code&gt; functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nextjs Router: &lt;code&gt;asPath&lt;/code&gt; vs &lt;code&gt;pathname&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;next/router&lt;/code&gt; router instance in our code has two useful properties for our &lt;code&gt;NextBreadcrumbs&lt;/code&gt; component; &lt;code&gt;asPath&lt;/code&gt; and &lt;code&gt;pathname&lt;/code&gt;. The router &lt;code&gt;asPath&lt;/code&gt; is the URL path as shown directly in the browser's URL bar. The &lt;code&gt;pathname&lt;/code&gt; is a more internal version of the URL that has the dynamic parts of the path replaced with their &lt;code&gt;[parameter]&lt;/code&gt; components.&lt;/p&gt;

&lt;p&gt;For example, consider the path &lt;code&gt;/post/abc&lt;/code&gt; from above.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;asPath&lt;/code&gt; would be &lt;code&gt;/post/abc&lt;/code&gt; as the URL is shown&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;pathname&lt;/code&gt; would be &lt;code&gt;/post/[post_id]&lt;/code&gt; as our &lt;code&gt;pages&lt;/code&gt; directory dictates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can use these two URL path variants to build a way to dynamically fetch information about the breadcrumb, so we can show more contextually appropriate information to the user.&lt;/p&gt;

&lt;p&gt;There is a lot going on below, so please re-read it and the helpful notes below a few times over if needed.&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;_defaultGetTextGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&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;_defaultGetDefaultTextGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Pulled out the path part breakdown because its&lt;/span&gt;
&lt;span class="c1"&gt;// going to be used by both `asPath` and `pathname`&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generatePathParts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pathStr&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;pathWithoutQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pathStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="mi"&gt;0&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;pathWithoutQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="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;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;NextBreadcrumbs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;getTextGenerator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;_defaultGetTextGenerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getDefaultTextGenerator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;_defaultGetDefaultTextGenerator&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;breadcrumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateBreadcrumbs&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;asPathNestedRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generatePathParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pathnameNestedRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generatePathParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&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;crumblist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&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;subpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&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;// Pull out and convert "[post_id]" into "post_id"&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pathnameNestedRoutes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&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="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="dl"&gt;""&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="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="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;href&lt;/span&gt; &lt;span class="o"&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="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;textGenerator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getTextGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getDefaultTextGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;href&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;href&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="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&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="nx"&gt;crumblist&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getTextGenerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getDefaultTextGenerator&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="c1"&gt;// ...rest below&lt;/span&gt;



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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;asPath&lt;/code&gt; breakdown was moved to a &lt;code&gt;generatePathParts&lt;/code&gt; function since the same logic is used for both &lt;code&gt;router.asPath&lt;/code&gt; and &lt;code&gt;router.pathname&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Determine the &lt;code&gt;param'eter that lines up with the dynamic route value, so&lt;/code&gt;abc&lt;code&gt;would result in&lt;/code&gt;post_id`.&lt;/li&gt;
&lt;li&gt;The nested route &lt;code&gt; param'eter and all associated query values (&lt;/code&gt;router.query&lt;code&gt;) are passed to a provided &lt;/code&gt;getTextGenerator&lt;code&gt; which will return either a &lt;/code&gt;null&lt;code&gt; value or a &lt;/code&gt;Promise` response that should return the dynamic string to use in the associated breadcrumb.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;useMemo&lt;/code&gt; dependency array has more dependencies added; &lt;code&gt;router.pathname&lt;/code&gt;, &lt;code&gt;router.query&lt;/code&gt;, and &lt;code&gt;getTextGenerator&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, we need to update the &lt;code&gt;Crumb&lt;/code&gt; component to use this &lt;code&gt;textGenerator&lt;/code&gt; value if provided for the associated crumb object.&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;function&lt;/span&gt; &lt;span class="nf"&gt;Crumb&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;defaultText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;textGenerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="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;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;defaultText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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="c1"&gt;// If `textGenerator` is nonexistent, then don't do anything&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="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textGenerator&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Run the text generator and set the text again&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalText&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;textGenerator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;finalText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;textGenerator&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;last&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text.primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&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;/Typography&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;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;underline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hover&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inherit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&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;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The breadcrumbs can now handle both static routes and dynamic routes cleanly, with the potential to display user-friendly values. While the above code is the component's business logic, this can all be used with a parent component that looks like the final example below.&lt;/p&gt;

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

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

&lt;span class="c1"&gt;// NextBreadcrumbs.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;_defaultGetTextGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&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;_defaultGetDefaultTextGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Pulled out the path part breakdown because its&lt;/span&gt;
&lt;span class="c1"&gt;// going to be used by both `asPath` and `pathname`&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generatePathParts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pathStr&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;pathWithoutQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pathStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="mi"&gt;0&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;pathWithoutQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="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;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;NextBreadcrumbs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;getTextGenerator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;_defaultGetTextGenerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getDefaultTextGenerator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;_defaultGetDefaultTextGenerator&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;breadcrumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateBreadcrumbs&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;asPathNestedRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generatePathParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pathnameNestedRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generatePathParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&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;crumblist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&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;subpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&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;// Pull out and convert "[post_id]" into "post_id"&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pathnameNestedRoutes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&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="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="dl"&gt;""&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="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="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;href&lt;/span&gt; &lt;span class="o"&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="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;asPathNestedRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;textGenerator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getTextGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getDefaultTextGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;href&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;href&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="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&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="nx"&gt;crumblist&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getTextGenerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getDefaultTextGenerator&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Breadcrumbs&lt;/span&gt; &lt;span class="nx"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;breadcrumb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;breadcrumbs&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;crumb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&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;Crumb&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;crumb&lt;/span&gt;&lt;span class="p"&gt;}&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;idx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;breadcrumbs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="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;/Breadcrumbs&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Crumb&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;defaultText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;textGenerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="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;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;defaultText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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="c1"&gt;// If `textGenerator` is nonexistent, then don't do anything&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="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textGenerator&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Run the text generator and set the text again&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalText&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;textGenerator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;finalText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;textGenerator&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;last&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text.primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&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;/Typography&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;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;underline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hover&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inherit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&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;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;An example of this &lt;code&gt;NextBreadcrumbs&lt;/code&gt; being used can be seen below. Note that &lt;code&gt;useCallback&lt;/code&gt; is used to create only one reference to each helper function which will prevent unnecessary re-renders of the breadcrumbs when/if the page layout component re-rendered. Of course, you &lt;em&gt;could&lt;/em&gt; move this out to the top-level scope of the file, but I don't like to pollute the global scope like that.&lt;/p&gt;

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

&lt;span class="c1"&gt;// MyPage.js (Parent Component)&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;NextBreadcrumbs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./NextBreadcrumbs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyPageLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// Either lookup a nice label for the subpath, or just titleize it&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getDefaultTextGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;subpath&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&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="s2"&gt;Posts&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="s2"&gt;settings&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="s2"&gt;User Settings&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="nx"&gt;subpath&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;titleize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subpath&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;// Assuming `fetchAPI` loads data from the API and this will use the&lt;/span&gt;
  &lt;span class="c1"&gt;// parameter name to determine how to resolve the text. In the example,&lt;/span&gt;
  &lt;span class="c1"&gt;// we fetch the post from the API and return it's `title` property&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTextGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/posts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="nx"&gt;param&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="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ...Whatever else... */&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;NextBreadcrumbs&lt;/span&gt;
        &lt;span class="nx"&gt;getDefaultTextGenerator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;getDefaultTextGenerator&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;getTextGenerator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;getTextGenerator&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="cm"&gt;/* ...Whatever else... */&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;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

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



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

&lt;/div&gt;




&lt;p&gt;This is one of my more in-depth and technical posts, so I hope you enjoyed it. Please comment or reach out regarding any issues to ensure consistency and correctness. Hopefully, this post taught you a few strategies or concepts about Nextjs.&lt;/p&gt;

&lt;p&gt;If you liked this or &lt;a href="https://dev.to/dan_starner"&gt;my other posts&lt;/a&gt;, please &lt;a href="https://buttondown.email/dan_starner" rel="noopener noreferrer"&gt;subscribe to my brand new Newsletter&lt;/a&gt; for weekly tech updates!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>react</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Switching Jobs in Tech Industry: 6 Key Lessons I've Learned</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Thu, 04 Nov 2021 21:22:40 +0000</pubDate>
      <link>https://dev.to/dan_starner/learned-lessons-of-switching-jobs-552g</link>
      <guid>https://dev.to/dan_starner/learned-lessons-of-switching-jobs-552g</guid>
      <description>&lt;p&gt;Switching jobs in the tech industry can be an interesting adventure, especially if you've had years of experience at your previous position. The dramatic change can spur many emotions, experiences, and situations in your life; both good and bad. Since I know many are either actively going through this process or are considering it I figured I would leave my thoughts.&lt;/p&gt;

&lt;p&gt;For context, I recently went through a job transition and started working at &lt;a href="https://www.heroku.com"&gt;Heroku&lt;/a&gt; - aka &lt;a href="https://salesforce.com"&gt;Salesforce&lt;/a&gt; - this past September. I won't go into &lt;em&gt;why&lt;/em&gt; I left my previous position, but I want to discuss my discoveries once I started to get settled into my new position. I'm only &lt;a href="https://www.timeanddate.com/date/durationresult.html?d1=13&amp;amp;m1=9&amp;amp;y1=2021ti=on&amp;amp;"&gt;~51 days&lt;/a&gt; into this position, so while there is a lot more to learn, I find my immediate experiences to be most helpful for those considering switching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Con: Productivity Will Be Tough at First
&lt;/h2&gt;

&lt;p&gt;During my transition phase, I decided to take almost a full month off between jobs. During this time, I went to Disney, played quite a bit of video games from my Steam backlog, and increased the amount of time that I was biking &amp;amp; exercising. It was all great! I felt like I had been revitalized as I was leaving behind some of the craziness of my old job, and I felt almost like a kid again with limited responsibilities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/26ufnwz3wDUli7GU0/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/26ufnwz3wDUli7GU0/giphy.gif" alt="I was lazy" width="500" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The downside to this time off was that it was &lt;em&gt;very&lt;/em&gt; difficult to get back into the groove of working again, as I struggled to find the motivation the first few weeks to actually want to work. Obviously the paycheck 💰 is the ultimate motivator, but it was still an adjustment to get back to always logging on 9-5 when I was getting used to being able to do whatever I wanted during the days of my unemployment.&lt;/p&gt;

&lt;p&gt;I will say that this process was made easier by switching to a fully remote position with a globally-located team. Since everyone on my team lives in different timezones, it made the work-day hours less strict, and I feel more able to get away from the keyboard in the middle of the day to do something for myself, like a bike ride or a nap. Now that I've been around for more than a month, that productivity and motivation is back, but wow, it was rough at the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grass is Always Greener on the Other Side
&lt;/h2&gt;

&lt;p&gt;Without getting into specifics, I left my job because I was unhappy with something in their current position and I saw the opportunities of a better existence in a new environment. While I understood that no job was going to be perfect, I didn't realize the number of similarities that different companies shared, both good &amp;amp; bad. Many processes, experiences, and mindsets were similar to how my old job operated.&lt;/p&gt;

&lt;p&gt;It was nice to know that I could already be familiar with some things, but there were other places where I could build off that knowledge to learn the unique aspects of my new team. The two positions weren't as different as I had anticipated though.&lt;/p&gt;

&lt;p&gt;There was one area that I let myself be blind to during the transition process... &lt;/p&gt;

&lt;h3&gt;
  
  
  Politics Exist Everywhere
&lt;/h3&gt;

&lt;p&gt;Without going into details, one of the reasons why I decided to find a new position was due to some internal bureaucratic decisions that I disagreed with. I got tired of playing politics for certain things that I thought were self-explanatory, and organization changes were occurring that went against some of the foundational ideals that my department had spent years building up.&lt;/p&gt;

&lt;p&gt;I had told myself, "Hey, these B.S politics are getting old, let's go somewhere else!" and I quickly realized that &lt;em&gt;every&lt;/em&gt; job has shitty politics and bureaucratic decisions to deal with. It's nothing against these companies, but at some point, business decisions have to be made and those are going to clash with engineering desires. Now, with that said, I believe that there is a line that can be crossed where one shouldn't keep putting up with politics, but to have the expectations of &lt;em&gt;never&lt;/em&gt; dealing with politics?...that is just unreasonably in the modern workplace.&lt;/p&gt;

&lt;p&gt;With this conclusion, I realized that I could change the angle that I was viewing some of these problems through. While I think the switch was still ultimately needed for me, I believe I've grown and have learned how to handle some of these situations better.&lt;/p&gt;

&lt;h3&gt;
  
  
  One Man's Trash...
&lt;/h3&gt;

&lt;p&gt;I find that the old adage of "&lt;em&gt;One man's trash is another man's treasure&lt;/em&gt;" is pretty true concerning jobs during &lt;a href="https://www.wired.com/story/great-resignation-misses-the-point/"&gt;&lt;em&gt;the Great Resignation&lt;/em&gt;&lt;/a&gt;. I saw pretty high levels of attrition at my old job - heck, I was one of those 😉 - and my new manager was pretty open that the team I was joining just faced some higher-than-average attrition levels.&lt;/p&gt;

&lt;p&gt;Does that raise red flags? 🚩🚩🚩🚩🚩🚩🚩 Hopefully not if you've done your due diligence before joining the company. I knew that this was occurring, and I knew the situation was changing; not for better or worse, it was just &lt;em&gt;changing&lt;/em&gt;. Changes meant new challenges and potential technology refreshers; it meant that I had the potential to make a bigger difference, and I was drawn to that opportunity.&lt;/p&gt;

&lt;p&gt;One thing that really excites me about starting a new job is that I can bring fresh perspectives to projects that may need it. If the same people are working on the same project for an extended period of time, it starts to create a silo of ideas and knowledge. By introducing new people to these projects - aka me onboarding - then these projects can get some new life. This allows them to be viewed in a way that allows us to make any needed changes that might have otherwise been overlooked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be Open to New Things
&lt;/h2&gt;

&lt;p&gt;"&lt;em&gt;Of course I'm open to new things, I just quit my job for heaven's sake!&lt;/em&gt;" I tell myself as I write this. But really though, I switched jobs and immediately was thinking about how to jump on a project that is most similar to what I'd been doing previously. I worked with a lot of &lt;a href="https://kubernetes.io/"&gt;Kubernetes&lt;/a&gt; cluster management at my old job, and I really wanted to just jump right into &lt;em&gt;more&lt;/em&gt; Kubernetes projects.&lt;/p&gt;

&lt;p&gt;Wanting to work with a specific technology is okay, but I wasn't really giving myself a full chance to grow more. With a new team &amp;amp; company came new opportunities, and I quickly realized that I was limiting myself by having such a small view of what I wanted to work on without knowing the full scope of projects and available capabilities. Once I saw myself doing this, I started giving other projects more of my time, and I found that I was learning a considerable amount in areas that I had limited familiarity in. This led to some of the confidence checks that I list below, but for the most part, its already been a pretty neat journey to connect the dots between my previous experience and the stuff that's totally new to me. &lt;strong&gt;By tackling these new problems, I am becoming a more well-rounded engineer&lt;/strong&gt;, and I can feel my confidence raising by a considerable amount in these areas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be Confident without Faking It
&lt;/h2&gt;

&lt;p&gt;So this section depends on what level you were at your previous company vs what you are joining as, but I feel like there's something for people of any level.&lt;/p&gt;

&lt;p&gt;For my situation, I was a newly minted Senior Engineer when leaving, and so I applied &amp;amp; received a Senior Engineer role at my new company. Do I deserve to be a senior right now? Well, my &lt;a href="https://medium.com/@dstarner/battling-imposter-syndrome-4650a73776cb"&gt;Imposter Syndrome&lt;/a&gt; says that I'm not good enough, but I've led projects, maintained critical systems, and built large complicated systems, so here I am as a senior. 🤷🏼‍♂️ I've solved enough problems and pulled out enough of my hairs to justify being a Senior - &lt;em&gt;I'm looking at you Jenkins/Groovy for most of those hairs pulled!!!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Being a senior brings with it a certain level of expectations and responsibility, which I was familiar with in my old job. The issue was that I was attempting to maintain that same level of expectations in a job where I barely knew what was going on. Most companies and teams expect onboarding new engineers to take at least a few months - based on the complexity of the system(s) - and my new team was no different. For some reason though, I was trying to shortcut this timeline and prove my skills as an engineer to those around me as quickly as possible, which led to some failed personal expectations and confidence issues that made me question my own abilities as an engineer.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1455300847328534531-311" src="https://platform.twitter.com/embed/Tweet.html?id=1455300847328534531"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1455300847328534531-311');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1455300847328534531&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: This isn't the blog post in question, but maybe I'll still post that other one implied in the Tweet&lt;/em&gt; 🤔&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't Stress About Proving Yourself
&lt;/h3&gt;

&lt;p&gt;If you are like me who puts a lot of pressure on themself to succeed, then switching jobs will probably lead to some stress &amp;amp; Imposter Syndrome from facing problems against an unfamiliar position. &lt;strong&gt;It's okay to struggle the first few months in a new position.&lt;/strong&gt; Like I mentioned, teams expect around 3-6 months to onboard engineers, so don't expect to start dropping big &lt;a href="https://www.atlassian.com/git/tutorials/making-a-pull-request"&gt;Pull Requests&lt;/a&gt; by the end of your second week.&lt;/p&gt;

&lt;p&gt;I wish I told myself this earlier though. I've been in this position for &lt;a href="https://www.timeanddate.com/date/durationresult.html?d1=13&amp;amp;m1=9&amp;amp;y1=2021ti=on&amp;amp;"&gt;less than 60 days&lt;/a&gt; and I've already almost burned myself out 😅 Why? Because &lt;strong&gt;I set an incredibly high bar for myself where I tried to match the work output that I had at my old position&lt;/strong&gt;. The issue there is that I don't have the 3 years of institutional knowledge at my new job that I gained at my old job. During the beginning of my onboarding, I was constantly telling myself &amp;amp; my manager that I &lt;em&gt;needed&lt;/em&gt; to get some big contributions out as soon as possible to prove I was worth hiring.&lt;/p&gt;

&lt;p&gt;It's almost impossible to hit the ground running when starting a new job though. For example, its difficult when you have a system and multiple environments as complicated as Heroku's tech. &lt;em&gt;Note: This is nothing against Heroku, its just **impossible&lt;/em&gt;* for anyone to learn a large company's tech stack within 2 months*.&lt;/p&gt;

&lt;p&gt;As time progresses, I will get more comfortable with the systems and my productivity will rise. It's going to be bad for my mental health and productivity to assume that I will be able to solve all of my new problems with the same ease that I did at my old job. My manager &amp;amp; team are not putting that level of pressure on me, so why am I putting it on myself? This is still something that I struggle with occassionally, but being away of the problem is the first step to solving it, and I just need to take a deep breath and let myself relax a bit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/yvLgbrii0xnLpG1TT2/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/yvLgbrii0xnLpG1TT2/giphy.gif" alt="Relax a bit" width="439" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pro: New Colleagues
&lt;/h2&gt;

&lt;p&gt;Switching has allowed me to get a brand new group of work peers! I worked with some really great engineers at my old job, and my new position is no different. Having the opportunity to work, learn, and grow with some of the best engineers in the world is something that I do not take for granted. While I'm not surprised by the fact that I am growing relationships to my new coworkers, I am surprised at how quickly it's happening. I already feel like I have the support of my managers, architects, and colleagues, and I am gaining their trust more and more as time goes on.&lt;/p&gt;

&lt;p&gt;I really want to keep in touch with many of my old peers, and I'm sure I will to some extent, but I'm excited for the challenges that I'll be able to overcome with my new team. I just need to meet all of them first, which is okay, because I've been trying really hard to reach out and schedule 1:1s with everyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initiate 1:1s with Everyone Around You
&lt;/h3&gt;

&lt;p&gt;An easy way that I started to build relationships with my team was by reaching out and setting up &lt;a href="https://www.small-improvements.com/resources/1-on-1-meetings/"&gt;1:1 introduction meetings&lt;/a&gt; with many of the people I work with day-to-day. This included those on my immediate team, sister teams, and my management chain.&lt;/p&gt;

&lt;p&gt;I tried to schedule meetings with the following approach, and I think it worked/is working pretty well!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First 2 weeks&lt;/strong&gt;: Meet with my manager and immediate teammates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First 4 weeks&lt;/strong&gt;: Meet with some members you are interacting with that are on sister teams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First 6 weeks&lt;/strong&gt;: Meet with any senior/principal engineers in your work tree and architects if those exist at your company&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Heroku also uses &lt;a href="https://www.donut.com/"&gt;Donut&lt;/a&gt; to connect different people in the organization, and I've been trying to actually schedule meetings with the other individuals the app pairs me with. It's made it somewhat easier to introduce myself across the organization, and to meet people in a way that is normally difficult for a remote-only workforce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Write Down Notes for EVERYTHING
&lt;/h2&gt;

&lt;p&gt;So I thought of this extra note about my onboarding experience, because I'm usually the one writing documentation or explaining a technical solution to others, and it's been a fun experience to turn the table on that notion as I get to learn everything my team interacts with.&lt;/p&gt;

&lt;p&gt;Sometimes i feel like I am asking too many questions, but I recognize that is just a part of the onboarding process, and I appreciate my colleagues' willingness to always help me get the answers I need to understand the systems a little bit better. To keep this process more enjoyable for them though, it's important that I note &amp;amp; remember as much of the information they give me as possible, so that I don't need to ask again. This note-taking process also helps, because it provides the opportunity to convert these notes into real documentation that can be used by others who find themselves in my same position...&lt;em&gt;then maybe they'll be asking me questions&lt;/em&gt;. 👀&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/3oz8xTUmZABI3PGwDe/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3oz8xTUmZABI3PGwDe/giphy.gif" alt="Writing notes" width="500" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As an extension, I'm glad that I found &lt;a href="https://obsidian.md/"&gt;Obsidian&lt;/a&gt; right as I was starting my new position, because my gosh I would be lost without a good note-taking app. Everything from onboarding requirements, to technical notes, to people's names/positions...you name it, and I have it in some notes from my onboarding. This has helped me stay on top of things while I start to climb the mountain of knowledge that my team works with, and I find that writing stuff down really helps me remember important details.&lt;/p&gt;




&lt;p&gt;I am excited to continuing growing in my new position, and I will probably have more posts to write about in the future regarding the cool work I get to do, but for now, I'm excited to just learn. The amount of information I'm getting to learn in my new position is truly incredible; it's like trying to drink from a fire hydrant sometimes. We'll see what I do and where the future takes me, but this transition period has been quite an experience, and I hope that some of my thoughts help you, wherever you may be in your own journey.&lt;/p&gt;

</description>
      <category>career</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Introspecting Python Parameter Values via Argument Binding</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Sun, 17 Oct 2021 01:23:07 +0000</pubDate>
      <link>https://dev.to/dan_starner/introspecting-python-parameter-values-via-argument-binding-1cok</link>
      <guid>https://dev.to/dan_starner/introspecting-python-parameter-values-via-argument-binding-1cok</guid>
      <description>&lt;p&gt;Sometimes it is important to map function arguments with their parameter values. Python offers basic &lt;strong&gt;keyword arguments&lt;/strong&gt; - which will be discussed below - but if arguments are passed either positionally, or are handled by "catch-alls", such as &lt;code&gt;*args&lt;/code&gt; or &lt;code&gt;**kwargs&lt;/code&gt;, then it quickly can become more difficult to deterministically map parameter names to their associated values in a function call.&lt;/p&gt;

&lt;p&gt;Some situations where this may be mapping may be desired include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Observability tools and call stack tracing&lt;/strong&gt; - Seeing what functions were called with what methods helps to determine how efficiently a program is running, or can help debug it when things go wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Determining function/task ownership for audit purposes&lt;/strong&gt; - If you system is a product used by organizations and users, it is important to have an easy, low-overhead way to determine what resources are being affected by a function call and who requested the change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Argument modification&lt;/strong&gt; - Sometimes a function's arguments need to be validated or modified before being used, and creating a decorator that can handle any arbituary parameter definitions may be useful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, we will dive into some more magical Python libraries and utilities that allow us to wrap and perform certain operations against arbituary function parameters. I will start with some Python basics - feel free to skip those sections as needed - before diving into some of the &lt;code&gt;Signature&lt;/code&gt; magic and it's real world use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functions Review
&lt;/h2&gt;

&lt;p&gt;Python, like most other programming languages, has the ability to define &lt;strong&gt;functions&lt;/strong&gt;. These functions usually define input &lt;strong&gt;parameters&lt;/strong&gt; and they output return values. &lt;strong&gt;Argument&lt;/strong&gt; values can then be provided to the function, which will perform some work and return a value. &lt;strong&gt;Note that I will try to use Python annotations where possible to make parameter types more clear.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&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;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Adds and returns the two arguments
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;num_1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;num_2&lt;/span&gt;

&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# returns 12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Quick note: Parameters are the &lt;em&gt;definitions&lt;/em&gt; of what variables a function defines, and &lt;em&gt;arguments&lt;/em&gt; are the actual runtime values that satisfy those parameters. In the example above, &lt;code&gt;num_1&lt;/code&gt; and &lt;code&gt;num_2&lt;/code&gt; are the parameter definitions, and the values of &lt;code&gt;5&lt;/code&gt; and &lt;code&gt;7&lt;/code&gt; are the arguments passed to the function.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Functions in Python can define arguments in two common ways: as &lt;strong&gt;positional arguments&lt;/strong&gt; and &lt;strong&gt;keyword arguments&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Positional Arguments
&lt;/h3&gt;

&lt;p&gt;Positional arguments are defined at the beginning of the parameter list, they are matched with their parameters &lt;em&gt;based on the order they are provided&lt;/em&gt;, and they have no default values. A positional argument &lt;em&gt;must&lt;/em&gt; be provided to the function when called.&lt;/p&gt;

&lt;p&gt;In the example above, since the function defines &lt;code&gt;num_1&lt;/code&gt; &lt;em&gt;and then&lt;/em&gt; &lt;code&gt;num_2&lt;/code&gt;, it means that the first argument passed (&lt;code&gt;5&lt;/code&gt;) will be saved to &lt;code&gt;num_1&lt;/code&gt; and the second argument passed (&lt;code&gt;7&lt;/code&gt;) will be saved to &lt;code&gt;num_2&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyword Arguments
&lt;/h3&gt;

&lt;p&gt;Keyword arguments are defined after positional arguments, and they can provide default values in the parameter definition, meaning that a matching argument does not need to be provided when the function is called. These arguments are provided using the parameter &lt;strong&gt;identifier&lt;/strong&gt;, which is the variable name given to the parameter. If they are given positionally, without the identifier, then they act in the same was a positional arguments.&lt;/p&gt;

&lt;p&gt;We could call &lt;code&gt;add&lt;/code&gt; using keyword arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# returns 18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how we have &lt;code&gt;&amp;lt;identifier&amp;gt;=&amp;lt;value&amp;gt;&lt;/code&gt;. We are now using keyword arguments to set &lt;code&gt;num_1&lt;/code&gt; and &lt;code&gt;num_2&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Default Keyword Arguments
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;add&lt;/code&gt; doesn't require keyword arguments by default, so lets consider the following function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;power&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;power&lt;/code&gt; parameter above is defined as a keyword argument by default, where if its not provided, it will be given a default argument value of &lt;code&gt;2&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# returns 4
&lt;/span&gt;&lt;span class="nf"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# returns 8
&lt;/span&gt;&lt;span class="nf"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# returns 8 as well
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Problem Statement
&lt;/h3&gt;

&lt;p&gt;Knowing how arguments and parameters work in Python, what if we want to write a program that prints out what arguments are given to a function, lining up positional and keyword arguments to a &lt;code&gt;dict&lt;/code&gt;ionary mapping. We will create a &lt;a href="https://pythonbasics.org/decorators/"&gt;decorator&lt;/a&gt; that accomplishes this.&lt;/p&gt;

&lt;p&gt;We are going to write the &lt;code&gt;log_function_call&lt;/code&gt; decorator function that will print out the arguments and return value of any function wrapped by the decorator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@log_function_call&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;power&lt;/span&gt;

&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# -&amp;gt; "pow was called with { num: 5, power: 2 }"
# -&amp;gt; 25
&lt;/span&gt;
&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# -&amp;gt; "pow was called with { num: 5, power: 3 }"
# -&amp;gt; 125
&lt;/span&gt;
&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# -&amp;gt; "pow was called with { num: 2, power: 4 }"
# -&amp;gt; 16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create this, we will need to solve the problem of determining what arguments given line up with the parameter definition in the function, so that we can print it out. In a more general sense:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can we intercept and determine what values are passed for a Python callable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this case, a &lt;strong&gt;callable&lt;/strong&gt; is anything that can take arguments and return a value, such as a function, method, or even a &lt;a href="https://www.w3schools.com/python/python_lambda.asp"&gt;&lt;code&gt;lambda&lt;/code&gt; function&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python's Signature Class
&lt;/h2&gt;

&lt;p&gt;Python has a built in &lt;code&gt;inspect&lt;/code&gt; module that provides many different utility functions and classes which allow the caller to get information and metadata about runtime variables during a program's execution. We can leverage this module and it's &lt;a href="https://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object"&gt;&lt;code&gt;Signature&lt;/code&gt; class&lt;/a&gt; to introspect a function's arguments to create a mapping of parameter names back to their values, &lt;em&gt;even if they are passed as position arguments&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;Without the &lt;code&gt;Signature&lt;/code&gt; class, only &lt;a href="https://docs.python.org/3/glossary.html#term-argument"&gt;keyword arguments&lt;/a&gt; can be easily converted into a &lt;code&gt;dict&lt;/code&gt; map, which limits the amount of introspection that can be performed. By using the  &lt;code&gt;Signature&lt;/code&gt; class, a generic decorator, such as &lt;code&gt;log_function_call&lt;/code&gt;, can convert &lt;em&gt;any&lt;/em&gt; arguments given to a function into a map of their parameter names and values. This is powerful as it allows us to create a more powerful decorator that can perform extra actions using a function's arguments, whether passed positionally or as keywords.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Look at Decorators
&lt;/h3&gt;

&lt;p&gt;Before diving into how the &lt;code&gt;Signature&lt;/code&gt; class can help us with the &lt;code&gt;log_function_call&lt;/code&gt; decorator, lets start setting up the decorator itself so that we can understand &lt;em&gt;that&lt;/em&gt; code before going further. While I assume some basic knowledge of Python decorators, I will briefly explain the code in what the decorator block would look like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_function_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
       Wrap the original function call to print the arguments before
       calling the intended function
       &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
       &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TODO: Print the arguments here!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By wrapping a function with this &lt;code&gt;log_function_call&lt;/code&gt; above, we will emit a print line before the function actually gets called. The only issue is that we are printing an unhelpful line instead of our actual argument values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signatures and Binding
&lt;/h3&gt;

&lt;p&gt;So we have a decorator that is intercepting the function call with access to the arbitrary positional argument - as a list represented as &lt;code&gt;*args&lt;/code&gt; - and keyword arguments - represented as the &lt;code&gt;**kwargs&lt;/code&gt; map. By generating a &lt;em&gt;signature&lt;/em&gt; of the arbitrarily wrapped function - &lt;code&gt;func&lt;/code&gt; in the example - we can merge these two generic argument objects into a single, declarative map of&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;parameter name&amp;gt;: &amp;lt;arg value&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which can then be acted upon easily to perform different operations, such as printing out the function call and arguments for auditing sake!&lt;/p&gt;

&lt;p&gt;In the following code, we are continuing off the previous decorator block with the addition of our introspection code; we are generating a signature of the wrapped function and then &lt;em&gt;binding&lt;/em&gt; the given arguments to the functions parameters, which returns a dictionary of parameter names to their passed values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_function_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Wrap the original function call to print the arguments before
        calling the intended function
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;func_sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Create the argument binding so we can determine what
&lt;/span&gt;        &lt;span class="c1"&gt;# parameters are given what values
&lt;/span&gt;        &lt;span class="n"&gt;argument_binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;argument_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argument_binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;

        &lt;span class="c1"&gt;# Perform the print so that it shows the function name
&lt;/span&gt;        &lt;span class="c1"&gt;# and arguments as a dictionary
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; was called with &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;argument_map&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real World Example
&lt;/h2&gt;

&lt;p&gt;So when can this actually be used? Well, for our project, we have a &lt;a href="https://www.djangoproject.com/"&gt;Django web application&lt;/a&gt; project that implements &lt;a href="https://docs.celeryproject.org/en/stable/index.html"&gt;Celery Asynchronous Tasks&lt;/a&gt;. We can schedule these tasks to be run by a background worker, but we require an audit log to determine &lt;em&gt;who&lt;/em&gt; started a task and &lt;em&gt;what organization&lt;/em&gt; the task is tied back to. &lt;/p&gt;

&lt;p&gt;For instance, if we have a task such as &lt;code&gt;resolve_membership&lt;/code&gt; that ensures the right members have the proper permissions, it may take in a &lt;code&gt;organization_id&lt;/code&gt; parameter of the organization to resolve membership for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;


&lt;span class="nd"&gt;@task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;-- Fanciness that just denotes the function as an async task
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve_membership&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TaskResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;organization_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Originization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&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="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;organization_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;do_something_with_the_org&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# arbituary code being run
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this task is created, scheduled, started, and competed, it's current state and result (&lt;code&gt;SUCCESS&lt;/code&gt;, &lt;code&gt;FAILED&lt;/code&gt;, &lt;code&gt;QUEUED&lt;/code&gt;, ...) will be stored in the database by Celery automatically. This gives us a queriable audit log of all &lt;code&gt;resolve_membership&lt;/code&gt; tasks that have been run, but it doesn't easily actually allow us to see which tasks correspond to which organizations. As a part of task scheduling, we wanted to create an audit log of these tasks and draw a relationship to what organizations they were affecting, and what - if any - user scheduled the tasks.&lt;/p&gt;

&lt;p&gt;We &lt;em&gt;could&lt;/em&gt; have performed this audit log creation explicitly in every task, like the following code example, but it would have gotten more difficult to manage as more tasks were created, or if any changes were made to the custom &lt;code&gt;TaskResult&lt;/code&gt; class. This would quickly become too cumbersome and would not result in a &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY program structure&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;


&lt;span class="nd"&gt;@task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;-- Fanciness that just denotes the function as an async task
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve_membership&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TaskResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;organization_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Originization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&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="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;organization_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Assign the organization to the task for audit logging purposes
&lt;/span&gt;    &lt;span class="c1"&gt;#   NOTE: this would need to be done on EVERY task
&lt;/span&gt;    &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;organization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;

    &lt;span class="c1"&gt;# Perform the actual task needed
&lt;/span&gt;    &lt;span class="nf"&gt;do_something_with_the_org&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# arbituary code being run
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using a wrapping decorator like the &lt;code&gt;log_function_call&lt;/code&gt; above, we were able to create a single &lt;code&gt;assign_ownership&lt;/code&gt; decorator that would wrap each task execution. This decorator would inspect the task arguments and provided some static arguments to the decorator itself, it would be able to create the same ownership relationship without needing to copy anything more than just the decorator name itself. This made it much easier to create the task audit log without having to worry about &lt;em&gt;how&lt;/em&gt; the ownership was actually being created. It reduced all of our individual implementations across tasks to a single function block that was much easier to manage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@audit_ownership&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org_param&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;organization_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve_membership&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TaskResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;organization_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Originization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&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="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;organization_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Perform the actual task needed
&lt;/span&gt;    &lt;span class="nf"&gt;do_something_with_the_org&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# arbituary code being run
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following code block is a paraphrased version of the decorator. Notice how we are using the &lt;code&gt;Signature&lt;/code&gt; class to load the value of a given parameter no matter how it was passed as a function; whether as a positional or keyword argument. Note that the following code is a bit advanced and does require some knowledge of how to define decorators in Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;audit_ownership&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org_param&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_param&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Wrapping a celery task, this will attempt to create an audit log
    tying the task execution back to an organization using passed arguments

    : org_param:  str  : function parameter that denotes the organization ID
    : task_param: str  : function parameter that denotes the task instance
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        This is the actual task decorator, but nested to allow for parameters
        to be passed on the decorator definition
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            The wrapper replaces the actual function call and performs the
            needed extra auditing work before calling the original function
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

            &lt;span class="c1"&gt;# create a function signature to introspect the call
&lt;/span&gt;            &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     
            &lt;span class="c1"&gt;# Create the argument binding so we can determine what
&lt;/span&gt;            &lt;span class="c1"&gt;# parameters are given what values
&lt;/span&gt;            &lt;span class="n"&gt;argument_binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;argument_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argument_binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;

            &lt;span class="c1"&gt;# Using the argument binding and decorator arguments, we can
&lt;/span&gt;            &lt;span class="c1"&gt;# fetch the task and organization, no matter how the function
&lt;/span&gt;            &lt;span class="c1"&gt;# was invoked. The power of argument introspection!!
&lt;/span&gt;            &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argument_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;task_param&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;organization_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argument_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;org_param&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;organization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Organization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&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="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# The actual logic is abstracted out to be more readable
&lt;/span&gt;            &lt;span class="c1"&gt;# Assume this function creates the relationship between the
&lt;/span&gt;            &lt;span class="c1"&gt;# task result instance and the organization instance
&lt;/span&gt;            &lt;span class="nf"&gt;assign_ownership_to_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;# this calls the original function
&lt;/span&gt;                                             &lt;span class="c1"&gt;# as it was original intended  
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;As an extra bonus, we made the &lt;code&gt;org_param&lt;/code&gt; decorator parameter also be a &lt;code&gt;callable&lt;/code&gt;, instead of being a &lt;code&gt;str&lt;/code&gt;, so that we could dynamically fetch the associated value. This was useful in situations where organization-owned resources were passed to the task instead of the organization itself, so we could have the callable load the child object and return a reference to its parent's organization.&lt;/p&gt;




&lt;p&gt;Python decorators that modify and perform operations using the provided arguments can rely on function signatures and argument bindings to determine parameter values as a part of their operations. This allows programs to utilize generalized decorators that can be reused across an application to perform operations such as debugging or audit-logging. Have some more examples of where this could be useful? Post them below!&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>django</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Maintaining Quality Documentation</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Thu, 09 Sep 2021 13:25:14 +0000</pubDate>
      <link>https://dev.to/dan_starner/keeping-documentation-debt-at-bay-clients-happy-53o</link>
      <guid>https://dev.to/dan_starner/keeping-documentation-debt-at-bay-clients-happy-53o</guid>
      <description>&lt;p&gt;&lt;strong&gt;Communication is difficult.&lt;/strong&gt; As remote work becomes more of a thing, as teams work across six+ timezones, and as companies are looking for rapid growth, &lt;strong&gt;it is paramount that technical knowledge is able to be communicated correctly and effectively.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Over my past few professional experiences, it became apparent that I had started to spend more of my time communicating ideas and interfaces with others, and less time actually developing them. Now, obviously the design phase before building something is critical, but I was more concerned about the amount of time I spent discussing a feature &lt;em&gt;after&lt;/em&gt; finishing it. I found that if I spent some time building a new feature for our product, I would spend &lt;em&gt;at least&lt;/em&gt; two or three times the amount of time answering Slack messages for users who wanted to use it or who had questions.&lt;/p&gt;

&lt;p&gt;I realized that while we had good technical engineering practices, &lt;strong&gt;we were lacking the communication channels &amp;amp; skills to allow our clients to effectively use the product&lt;/strong&gt;. This resulted in a fair amount of precious engineering time lost to our on-call team member basically acting as tech support on Slack, as they'd have to answer very similar questions multiple times throughout the day.&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%2Fmedia2.giphy.com%2Fmedia%2F3orif0rjs49gsPWg1y%2Fgiphy.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%2Fmedia2.giphy.com%2Fmedia%2F3orif0rjs49gsPWg1y%2Fgiphy.gif" alt="Customer Support"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Curse of Success
&lt;/h2&gt;

&lt;p&gt;Our product was still young, but it was quickly growing in popularity at the company. It was a cluster configuration and deployment system that managed many of our internal services. I was one of the lead engineers responsible for developing this ecosystem, and we had built out quite a few systems and solutions to solve all different categories of technical problems.&lt;/p&gt;

&lt;p&gt;We were able to implement these problems very well, but we had grow &lt;em&gt;so&lt;/em&gt; fast that we started to realize some problems with the way we were communicating and sharing our product with the rest of the company.&lt;/p&gt;

&lt;p&gt;As more users found the tooling and started using it, we had more users who ranged from "&lt;em&gt;Oh this is neat&lt;/em&gt;", to our advanced users &amp;amp; internal team members who knew the full "&lt;em&gt;in's and out's&lt;/em&gt;" of the tools. Somehow, we had to make sure every end user and effected client could effectively use our tooling to solve their deployment problems.&lt;/p&gt;

&lt;p&gt;This growth problem was easily manageable when we had just a few teams using the product, as many of those users had been with us since the start of the product and could use it moderately well. We had a page or two of loosely organized &lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt; documents that briefly explained how to get different environments up and running, and the users were expected to sort-of just figure out the rest.&lt;/p&gt;

&lt;p&gt;Our documentation at the time was like the Owl-drawing tutorial below; &lt;em&gt;we will walk you through the left-side picture, and then just throw you to the fire and hope you figure out all the details as you go to production.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--p65ClE-A--%2Fc_imagga_scale%2Cf_auto%2Cfl_progressive%2Ch_900%2Cq_auto%2Cw_1600%2Fhttps%3A%2F%2Fi.kym-cdn.com%2Fphotos%2Fimages%2Fnewsfeed%2F000%2F572%2F078%2Fd6d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--p65ClE-A--%2Fc_imagga_scale%2Cf_auto%2Cfl_progressive%2Ch_900%2Cq_auto%2Cw_1600%2Fhttps%3A%2F%2Fi.kym-cdn.com%2Fphotos%2Fimages%2Fnewsfeed%2F000%2F572%2F078%2Fd6d.jpg" alt="Rest of the F**king Owl"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tribal Knowledge and the Silo of Doom
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Tribal knowledge is any unwritten information that is not commonly known by others within a company.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We quickly realized that our team and the original user base had quite a bit of tribal knowledge in the system. As our product grew in popularity and was used by new users, they did not have this knowledge that seemed &lt;em&gt;"trivial"&lt;/em&gt; to us, but in reality was confusing for these first-time users. &lt;strong&gt;We had no way to effectively communicate the silo of knowledge that we had developed over the years of building &amp;amp; using the product&lt;/strong&gt;. It's very difficult for people to use something if they don't know how to use it. They are even less likely to use it if they see others describe it as "easy" without seeing the path to attaining that knowledge. When people notice this gap of knowledge between it's users with no discernible way to resolve it, &lt;strong&gt;the reputation of the product is at stake.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our users wanted to learn our product, and we &lt;em&gt;wanted&lt;/em&gt; them to love the product, but &lt;strong&gt;we had a documentation problem.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cue the Documentation Attempts
&lt;/h2&gt;

&lt;p&gt;Our team set off to close this documentation gap between our users and tooling. We had three main areas of tooling &amp;amp; associated documentation that needed addressed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A web user interface&lt;/strong&gt; that provided a nice overview and data visualization of our system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A RESTful API&lt;/strong&gt; that provided all of the deployment data and configurations for the web UI and any integrated services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A CLI for command-line-based operations&lt;/strong&gt; which allowed for local development and cluster configuration management&lt;/li&gt;
&lt;li&gt;Our overall concepts and tutorials so that users could learn the system from a high level across the aforementioned tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was important for us to solve our documentation problem for each of the services above, because we know that good project documentation allowed the following situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improved onboarding of new users and new team members&lt;/li&gt;
&lt;li&gt;Reduction in support questions by users&lt;/li&gt;
&lt;li&gt;Ability to quickly link users to documentation sections for answers and concepts&lt;/li&gt;
&lt;li&gt;Increased reputation and usage due to (hopefully) being more user-friendly&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Documenting the Web Interface
&lt;/h3&gt;

&lt;p&gt;Web interfaces are hard, because they should be designed intuitively and cleanly so that users familiar with the interface can interact with it as efficient as possible. This presents a challenge for newer users who are unfamiliar with the system, as the layouts and functions of components will be totally new to them.&lt;/p&gt;

&lt;p&gt;To overcome these documentation issues in our web interface, we had both &lt;strong&gt;proactive documentation&lt;/strong&gt; through means of initial popups that would call out certain elements on the first visit to a page type, and &lt;strong&gt;passive documentation&lt;/strong&gt; that would open a sidebar containing page-specific documentation when an icon button was pressed. We felt like this approach was a good trade-off of introducing new users to elements without being too intrusive. &lt;/p&gt;

&lt;p&gt;The highlighted element pop-ups used &lt;a href="https://reactour.vercel.app/#tourprovider" rel="noopener noreferrer"&gt;Reactour&lt;/a&gt; to display their information. To ensure that we covered future changes to the steps, we would hash the list of generated steps and save them to the client's &lt;code&gt;localStorage&lt;/code&gt; in the form of &lt;code&gt;[page URL]: &amp;lt;steps hash&amp;gt;&lt;/code&gt; where the &lt;code&gt;[page URL]&lt;/code&gt; was of generic form, such as &lt;code&gt;/settings&lt;/code&gt; or &lt;code&gt;/users/[user_id]&lt;/code&gt; so that we only showed the steps once per dynamically-rendered page type. These pop-ups provided very basic introductory information about the elements that they highlighted, such as what functions they performed.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/elrumordelaluz" rel="noopener noreferrer"&gt;
        elrumordelaluz
      &lt;/a&gt; / &lt;a href="https://github.com/elrumordelaluz/reactour" rel="noopener noreferrer"&gt;
        reactour
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Tourist Guide into your React Components
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;For our passive, yet detailed documentation, we allowed each page to pass a markdown page source to our core layout component that would &lt;a href="https://reactjs.org/docs/code-splitting.html#reactlazy" rel="noopener noreferrer"&gt;lazy load&lt;/a&gt; and render the markdown into a nice right-side drawer that could be toggled open and closed. This sidebar would be used for presenting more details about the overall page usage and components that existed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documenting the RESTful API
&lt;/h3&gt;

&lt;p&gt;Our RESTful API was actually the easiest to document, because we were able to rely on third-party libraries from the start. It was a &lt;a href="https://www.djangoproject.com/" rel="noopener noreferrer"&gt;Python Django&lt;/a&gt; and &lt;a href="https://www.django-rest-framework.org/" rel="noopener noreferrer"&gt;Django REST Framework&lt;/a&gt; project that leveraged the &lt;a href="https://github.com/axnsan12/drf-yasg" rel="noopener noreferrer"&gt;&lt;code&gt;drf-yasg&lt;/code&gt; OpenAPI generator library&lt;/a&gt; to create &lt;a href="https://swagger.io/specification/" rel="noopener noreferrer"&gt;OpenAPI&lt;/a&gt; and &lt;a href="https://swagger.io/" rel="noopener noreferrer"&gt;Swagger&lt;/a&gt; compatible documentation.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/axnsan12" rel="noopener noreferrer"&gt;
        axnsan12
      &lt;/a&gt; / &lt;a href="https://github.com/axnsan12/drf-yasg" rel="noopener noreferrer"&gt;
        drf-yasg
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Automated generation of real Swagger/OpenAPI 2.0 schemas from Django REST Framework code.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;This library allowed us to write the API documentation in-place throughout the route definitions, and &lt;code&gt;drf-yasg&lt;/code&gt; would render documentation in either the form of an OpenAPI schema or as an interactive &lt;a href="https://github.com/Redocly/redoc" rel="noopener noreferrer"&gt;Redoc&lt;/a&gt;/&lt;a href="https://swagger.io/tools/swagger-ui/" rel="noopener noreferrer"&gt;Swagger UI&lt;/a&gt; web page. This meant that &lt;strong&gt;we were generating documentation from our code&lt;/strong&gt; and there was no extra developer steps to change documentation if a feature or route changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documenting the CLI
&lt;/h3&gt;

&lt;p&gt;Our command line interface was a &lt;a href="https://golang.org/" rel="noopener noreferrer"&gt;Golang&lt;/a&gt; binary executable that was based off of the &lt;a href="https://cobra.dev/" rel="noopener noreferrer"&gt;&lt;code&gt;cobra&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/spf13/viper" rel="noopener noreferrer"&gt;&lt;code&gt;viper&lt;/code&gt;&lt;/a&gt; libraries which made creating advanced CLIs very easy.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/spf13" rel="noopener noreferrer"&gt;
        spf13
      &lt;/a&gt; / &lt;a href="https://github.com/spf13/cobra" rel="noopener noreferrer"&gt;
        cobra
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Commander for modern Go CLI interactions
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Amongst the many neat features of &lt;code&gt;cobra&lt;/code&gt;, we were able to extend the &lt;a href="https://github.com/spf13/cobra/blob/master/doc/md_docs.md" rel="noopener noreferrer"&gt;Markdown documentation generation feature&lt;/a&gt; which would create rich user documentation for each of the available commands. We would generate this documentation at release time and either deploy the static files as a &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages site&lt;/a&gt;, or integrate them with our generic documentation below. We made sure to always include older release documentation for users who were not on the latest CLI version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documenting the System
&lt;/h3&gt;

&lt;p&gt;With the individual components laid out, it was finally time to document the entire system so that it made sense to users. We actually jumped through three or four documentation generators over the three years that I worked on the project.&lt;/p&gt;

&lt;p&gt;The loose timeline of our documentation services was:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt; → &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; → &lt;a href="https://docsify.js.org/" rel="noopener noreferrer"&gt;Docsify&lt;/a&gt; → &lt;a href="https://docusaurus.io/" rel="noopener noreferrer"&gt;Docusaurus&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Jekyll
&lt;/h4&gt;

&lt;p&gt;As mentioned earlier, our documentation started in &lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt;, and it worked decently well for us. The two main drawbacks for us was that&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It slowly grew too difficult to manage large documentation as links &amp;amp; page references needed to be manually updated, and it became more difficult to manage as the number of pages grew&lt;/li&gt;
&lt;li&gt;Our team did not use Ruby, so the development process of putting docs together put users on an unfamiliar path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also found that some of the documentation templates seemed to be dated, and we were just looking for something that looked more refreshed and clean, but that's more of an subjective reason to switch than a technical one.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hugo
&lt;/h4&gt;

&lt;p&gt;We gave &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; a shot next, and it provided some more validation and speed in terms of development time. Since the associated program is just a binary, it could be very easily installed on our machines and in workflows without much fuss.&lt;/p&gt;

&lt;p&gt;Hugo provided more flexibility and validation across our documentation, and we quickly grew our documentation and made it look much more organized using the &lt;a href="https://learn.netlify.app/en/" rel="noopener noreferrer"&gt;Hugo Learn theme&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hugo was great, but it required a &lt;em&gt;Build&lt;/em&gt; step to be run between pages in a GitHub repository could show up in a GitHub pages site. We built workflows to handle this on merges to &lt;code&gt;main&lt;/code&gt;, but it wasn't perfect and would occasionally fail. This generation step had us looking at client-side generators again, and we ran into...&lt;/p&gt;

&lt;h4&gt;
  
  
  Docsify
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://docsify.js.org/" rel="noopener noreferrer"&gt;Docsify&lt;/a&gt; is a client-side only documentation generator. As a user requests a documentation page, it would fetch the associated &lt;code&gt;.md&lt;/code&gt; file and render it all client-side. That meant that our documentation deployment process was simple again, as we could just push changes to the &lt;code&gt;main&lt;/code&gt; branch and &lt;em&gt;BOOM&lt;/em&gt; they would show up within seconds.&lt;/p&gt;

&lt;p&gt;The other draw of Docsify was that there were some really slick minimalist themes that we found and really enjoyed. We could pair these with &lt;a href="https://github.com/docsifyjs/awesome-docsify#plugins" rel="noopener noreferrer"&gt;the long list of extensions&lt;/a&gt; and provide users with clean, yet extensive documentation.&lt;/p&gt;

&lt;p&gt;The drawback with Docsify was that as our documentation continued to grow, it started to slow down the rendering process as the initial load would require fetching &lt;em&gt;a lot&lt;/em&gt; of information. On top of this, the searching functionality became more unusable as it wasn't a great interface, and it had to search &lt;em&gt;all&lt;/em&gt; files in a flat format to generate the results. My final gripe with Docsify was one of the reasons that I was initially drawn to it; anything more than generic markdown requires plugins. This introduced a bunch of documentation dependencies on libraries that didn't seem &lt;em&gt;fully&lt;/em&gt; legit and managed, and we found ourselves having to write custom plugins a lot to make things render as desired.&lt;/p&gt;

&lt;h4&gt;
  
  
  Docusaurus
&lt;/h4&gt;

&lt;p&gt;After jumping from documentation generator to generator, we finally settled on &lt;a href="https://docusaurus.io/" rel="noopener noreferrer"&gt;Docusaurus&lt;/a&gt;. It introduced a build step again similar to Hugo, but we found that the trade off was worth it, as Docusaurus brought &lt;em&gt;a bunch of really nice features with it&lt;/em&gt;. Since everything was just React under the hood that could be customized, we naturally gravitated to it as we already had React experience.&lt;/p&gt;

&lt;p&gt;We found that it was much easier to customize and extend Docusaurus as compared to our other generators. This meant that we could provide the full &amp;amp; rich experience that we wanted. Our users also responded the best to this iteration of our documentation, but that may partially be accredited to us spending more time to organize the content as well as the overall layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Documentation Tips
&lt;/h2&gt;

&lt;p&gt;The following tips and suggestions are just a few of the ways that our team overcame our documentation debt challenges, and how we ensured that our documentation was always in an acceptable state, no matter the level of user who was reading them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating Documentation from Code
&lt;/h3&gt;

&lt;p&gt;As previously mentioned, our API and CLI documentation was generated directly from the underlying code. I highly recommend this strategy of &lt;em&gt;Documentation-from-Code&lt;/em&gt;; it makes the process of writing great documentation that much easier. Having one interface for developers to add and document features makes it more likely that they will actually write documentation.&lt;/p&gt;

&lt;p&gt;Most languages and application types have some set of libraries to assist in generating documentation from code, and I highly recommend implementing them in your user-facing projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Less More Often
&lt;/h3&gt;

&lt;p&gt;It's difficult to climb a mountain in a day, but it becomes easier if you take one small step periodically. Documentation works the same way; writing all of your team's documentation at once will most likely cause stress and annoyance. It's strongly encourages to write documentation in small increments, hopefully at the time that the associated feature is written.&lt;/p&gt;

&lt;p&gt;For older features that need documentation, don't fret; just try to write a few paragraphs, or even sentence, whenever you have some free time. As long as you are slowly crunching way at the documentation debt, you are improving the situation.&lt;/p&gt;

&lt;p&gt;Finally, I found that it was easy to add/improve documentation for a certain feature if a user had just asked a question about the topic. This showed either a gap in our documentation, or a discrepancy that confused users. By tackling these issues one at a time, it was easier to ensure good documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Organizing Documentation by User Story
&lt;/h3&gt;

&lt;p&gt;As our product matured, our documentation evolved from a list of individual available features to tutorials and guides based around common user workflows. We had a section of tutorials for our common use cases that included users just getting started with the system, all the way to some of our most advanced use cases.&lt;/p&gt;

&lt;p&gt;Being a deployment system, we broke up our documentation into cluster timeline user stories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Day 0&lt;/strong&gt;: Gathering and provisioning infrastructure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 1&lt;/strong&gt;: Implementing cluster tools such as monitoring and alerting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 2&lt;/strong&gt;: Deploying application and configuring DNS amongst other things&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layout provided users with a pretty linear approach to reading our documentation. They could either get the brief steps through a tutorial, or they could follow our guides in order to use the system. Our support questions have reduced slightly since we switched to this more linear documentation organization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Devoting Time for Documentation Debt
&lt;/h3&gt;

&lt;p&gt;It's not too unheard of to hear about engineering teams having hackathons during the workday to allow contributors to work on neat projects and squash &lt;a href="https://xkcd.com/2138/" rel="noopener noreferrer"&gt;technical debt&lt;/a&gt;. I would say teams need to go one step farther and have &lt;em&gt;Documentation Hackathons&lt;/em&gt;, or at least give individuals the ability to fully devote time to both their client-facing and internal documentation.&lt;/p&gt;

&lt;p&gt;This allows some time to "&lt;em&gt;refresh&lt;/em&gt;" the content and ensure its fully up to date.&lt;/p&gt;




&lt;p&gt;Good documentation practices are key to ensuring happy users and keeping technical support questions to a minimum. Hopefully the above experiences, technologies, and tips help you and your team present your products as positively and completely as possible!&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>productivity</category>
      <category>development</category>
      <category>ux</category>
    </item>
    <item>
      <title>Updating My GitHub Profile README</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Wed, 05 Aug 2020 05:06:54 +0000</pubDate>
      <link>https://dev.to/dan_starner/updating-my-github-profile-readme-2mch</link>
      <guid>https://dev.to/dan_starner/updating-my-github-profile-readme-2mch</guid>
      <description>&lt;p&gt;A few weeks ago, &lt;a href="https://dev.to/github"&gt;GitHub&lt;/a&gt; released full support for adding READMEs to profile pages. This allows users to spice up their pages with greetings, contact &amp;amp; professional information, or to &lt;a href="https://github.com/timburgan"&gt;create a fun, random game of chess&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1286724102099816448-215" src="https://platform.twitter.com/embed/Tweet.html?id=1286724102099816448"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1286724102099816448-215');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1286724102099816448&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;I've been meaning to create one for a few weeks, and apparently midnight on a Tuesday was the right time for me to actually go through with it. All it takes is to create a new repository named after your username with a &lt;code&gt;README.md&lt;/code&gt; file in it! My profile README includes the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A nice introduction to greet people&lt;/li&gt;
&lt;li&gt;Some quick facts about myself to get people up to speed&lt;/li&gt;
&lt;li&gt;A disclaimer that my public GitHub code may not be up to par with my best skills 😅&lt;/li&gt;
&lt;li&gt;My current projects, volunteering efforts, and hobbies&lt;/li&gt;
&lt;li&gt;My latest Dev blog posts (credit to &lt;a href="https://dev.to/gautamkrishnar"&gt;@gautamkrishnar&lt;/a&gt; for the workflow)&lt;/li&gt;
&lt;li&gt;My GitHub stats across repositories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can &lt;a href="https://github.com/dstarner"&gt;visit my profile&lt;/a&gt;, or just check out the screenshot below. What kind of stuff have you seen, or what does your profile README look like?!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CB5MoJ9w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/4iumjzayrjj1sf35zdef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CB5MoJ9w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/4iumjzayrjj1sf35zdef.png" alt="GitHub Profile" width="800" height="1138"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>showdev</category>
    </item>
    <item>
      <title>What Tool Can You Never Remember or Get Good At?</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Thu, 05 Mar 2020 01:16:46 +0000</pubDate>
      <link>https://dev.to/dan_starner/what-tool-can-you-never-remember-or-get-good-at-391j</link>
      <guid>https://dev.to/dan_starner/what-tool-can-you-never-remember-or-get-good-at-391j</guid>
      <description>&lt;p&gt;What program, application, or shortcut have you never been able to get behind, even though most people tout up as the best thing for developing? It could be either because you don't care to learn/use it, or because you always forget &lt;em&gt;how&lt;/em&gt; to use it.&lt;/p&gt;

&lt;p&gt;For me, its &lt;code&gt;tmux&lt;/code&gt;. I realized it today when I kept complaining about my &lt;code&gt;ssh&lt;/code&gt; session crashing and having to start again. My teammate reminded me that &lt;code&gt;tmux&lt;/code&gt; has persistent sessions, but after 3 or 4 times of trying to use it more often, I just find it frustrating and never really use it. Maybe one day I'll actually get good at using it, but for now it just seems like a lot of trouble and time that will slow me down in the short term.&lt;/p&gt;

&lt;p&gt;Some other honorable mentions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;vim&lt;/code&gt; / &lt;code&gt;emacs&lt;/code&gt;: I know the basics of &lt;code&gt;vim&lt;/code&gt;, but holy heck am I slow at making changes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;curl&lt;/code&gt;: I basically need to relearn &lt;code&gt;curl&lt;/code&gt; every time I want to make a simple request from the command line&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>discuss</category>
      <category>tools</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Myth of Sisyphus, Failure, &amp; the Meaning of Imperfect Code</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Wed, 26 Feb 2020 02:12:07 +0000</pubDate>
      <link>https://dev.to/dan_starner/the-myth-of-sisyphus-failure-the-meaning-of-imperfect-code-25e3</link>
      <guid>https://dev.to/dan_starner/the-myth-of-sisyphus-failure-the-meaning-of-imperfect-code-25e3</guid>
      <description>&lt;p&gt;For years as a student and into my first few years as a junior engineer, I felt dirty. Something felt wrong about the code I wrote. No matter how hard I tried, things just didn't always line up and it bothered me. &lt;em&gt;"What is the point of writing code if its not the best it can be?&lt;/em&gt;" I would ask myself. If my editor was a pencil and paper, I would have gone through all of the world's supplies of erasers before ever coming to a solution, because I felt like the code I was writing wasn't good enough to give to a client or to deserve a job. This made certain parts of my early career miserable, slowed down projects, led to burnout, and even made me reconsider my life choices a few times.&lt;/p&gt;

&lt;p&gt;As I've grown and learned from the experienced individuals around me, I've come to accept my imperfect code and imperfect solutions. While coming to this acceptance, I was able to draw some notable comparisons to one of my favorite ancient Greek myths, the tale of Sisyphus the king. He was tasked with rolling a boulder up a hill for all of eternity only to have it fall back to the bottom, all because of a con he attempted where he tried to cheat death. Sounds pretty grim, right? But there's a lot to learn from this myth and a lot to be optimistic about when digging deeper into it. Even thousands of years later, we can relate this myth to the daily grind of software engineering and programming.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Note Before Going Further
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“There are three things I have learned never to discuss with people... Religion, Politics, and The Great Pumpkin.”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Charles M. Schulz, Creator of Peanuts&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm going to mention a piece of writing throughout this post titled &lt;a href="https://www2.hawaii.edu/~freeman/courses/phil360/16.%20Myth%20of%20Sisyphus.pdf"&gt;&lt;em&gt;The Myth of Sisyphus&lt;/em&gt; by Albert Camus&lt;/a&gt; that talks about existentialism and the absurdity of life. I cannot stand on a soap box and argue whether or not Camus's thinking on life, death, and the meaning of it all is correct, I simply find the story interesting and drew some conclusions to my own thinking about the Software Development Life Cycle and what it means to be a Software Engineer. It &lt;em&gt;is&lt;/em&gt; however an extremely interesting book no matter your background, so I recommend giving it a read if you are looking for some crazy deep philosophy and just a few minor cases of existentialist dread.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Common Scenarios of Mine
&lt;/h2&gt;

&lt;p&gt;The two following situations seem to occur a lot as a junior engineer, and I know many colleagues and former classmates who let these situations get to them, chasing them away from the software industry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Changing &amp;amp; Misunderstood Requirements
&lt;/h3&gt;

&lt;p&gt;Have you ever had those days of programming when you seem to be on a roll, and then suddenly you realize any one of the following: 1) you missed an important edge case, 2) misunderstood either the problem or the solution, or 3) the requirements were changed out from under you? After working long enough in the software world, one, if not all three of these situations will happen to you, and its just upsetting, especially if you have already have been deep into the project or a particular implementation. This causes you to reevaluate, start over, and reset your entire thinking on the problem, potentially burning away hours or even days of time from your previous attempt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oLSZUCsV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/qyrkcmyvc404jqe2f5uv.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oLSZUCsV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/qyrkcmyvc404jqe2f5uv.JPG" alt="Project Requirements" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution Stinks of Code Smell
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"Code smells are a set of common signs which indicate that your code is not good enough and it needs refactoring to finally have a clean code."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mohamed Aladdin post on &lt;a href="https://codeburst.io/write-clean-code-and-get-rid-of-code-smells-aea271f30318"&gt;Writing Clean Code&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quite frequently I will design a solution that seems perfectly reasonable and easy to explain in my head, only to find that it is difficult to implement in real life. I'll tell myself something along the lines of &lt;em&gt;"Oh yea, I'll just create class &lt;code&gt;X&lt;/code&gt; and parent interface &lt;code&gt;Y&lt;/code&gt; and the code will be awesome!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;...then you go to write this &lt;em&gt;perfect&lt;/em&gt; implementation, annnnnddddd just nothing seems to come out as well as it looked in your head? Either interface &lt;em&gt;Y&lt;/em&gt; just doesn't seem to be working, or maybe the dependency injection you thought would be smart in class &lt;em&gt;X&lt;/em&gt; is backfiring in your face.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5P-l7OOO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://image.slidesharecdn.com/code-smells-130917082754-phpapp01/95/excuse-me-butyour-code-smells-1-638.jpg%3Fcb%3D1409760928" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5P-l7OOO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://image.slidesharecdn.com/code-smells-130917082754-phpapp01/95/excuse-me-butyour-code-smells-1-638.jpg%3Fcb%3D1409760928" alt="Your Code Smells" width="638" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code is the Boulder
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"[A]ccording to the Greek myth, [Sisyphus] was punished for all eternity to roll a rock up a mountain only to have it roll back down to the bottom when he reaches the top."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.sparknotes.com/philosophy/sisyphus/summary/"&gt;SparkNotes on &lt;em&gt;The Myth of Sisyphus&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don't tell my high school literature teachers that I quoted SparkNotes, but as previously mentioned, Sisyphus's punishment was to role a boulder up a hill, only to watch it roll back down as he approached the top. After all the time and effort he put in, he would watch himself fail and have to start back from the beginning again.&lt;/p&gt;

&lt;p&gt;The aforementioned mental blocks and difficulties are the problems that I face in my personal and professional life; &lt;strong&gt;they are my boulder(s)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_kErGSEq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://78.media.tumblr.com/b05708a79cbff94032acb3cd52fbeaf6/tumblr_opf10o4gZD1rcqnnxo1_500.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_kErGSEq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://78.media.tumblr.com/b05708a79cbff94032acb3cd52fbeaf6/tumblr_opf10o4gZD1rcqnnxo1_500.gif" alt="Patrick Start Rolling a Rock" width="500" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Software development is a lot like the myth of Sisyphus; it is never-ending, is always pushing forwards, and has the potential to hand you big failures when you are least expecting it.&lt;/p&gt;

&lt;p&gt;I think the Dev user &lt;a href="https://dev.to/jacksonelfers"&gt;jacksonelfers&lt;/a&gt; says it pretty well in his comment below.&lt;/p&gt;


&lt;div class="liquid-comment"&gt;
    &lt;div class="details"&gt;
      &lt;a href="/jacksonelfers"&gt;
        &lt;img class="profile-pic" src="https://res.cloudinary.com/practicaldev/image/fetch/s--nCdVFI3n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/practicaldev/image/fetch/s--EXudhOYS--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_50%2Cq_auto%2Cw_50/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/130881/a7799382-c329-4204-832c-7139487da0dd.jpg" alt="jacksonelfers profile image"&gt;
      &lt;/a&gt;
      &lt;a href="/jacksonelfers"&gt;
        &lt;span class="comment-username"&gt;Jackson Elfers&lt;/span&gt;
      &lt;/a&gt;
      &lt;span class="color-base-30 px-2 m:pl-0"&gt;•&lt;/span&gt;

&lt;a href="https://dev.to/jacksonelfers/comment/97k4" class="comment-date crayons-link crayons-link--secondary fs-s"&gt;
  &lt;time class="date-short-year"&gt;
    Mar 6 '19
  &lt;/time&gt;

&lt;/a&gt;

    &lt;/div&gt;
    &lt;div class="body"&gt;
      

&lt;p&gt;Programming is a lot like the Greek mythology of Sisyphus. Never ending, never complete, sometimes things go up in flames you were completely confident about. Then there are moments that remind you the perks of being able to talk to computers.&lt;/p&gt;



    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Our responsibilities with programming are never-ending, and sometimes burn us. Even if we "&lt;em&gt;complete&lt;/em&gt;" a project, there may be bug fixes, patches, new features, or at least another project behind it waiting to fill more time and space in our lives. In this regard, &lt;strong&gt;we as programmers are always pushing our boulders up our own hills.&lt;/strong&gt; There's always tech debt, the next project, the next requirement, or a bug fix that needs to be done. These are our responsibilities and our duties in the software and programming industry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iterative Progression, Not Immediate Perfection
&lt;/h3&gt;

&lt;p&gt;Since we know &lt;strong&gt;there is always work to be done, it becomes impossible to write perfect code, because perfect code would mean there is nothing left to do.&lt;/strong&gt; That lifts a massive burden off of our backs to feel like we have to be perfect. Since we know that we will always be rolling our metaphorical boulders up the hill of software development, that opens us up to allow for failures, learning, and so much more.&lt;/p&gt;

&lt;p&gt;As a junior engineer, I've messed up projects, missed deadlines, and have seen my "boulder" roll back down the hill as represented by my failures. Previously, I would let myself be defined by these failures, and I beat myself down every time something went wrong. What I failed to notice is that everyone around me - including the hot shot senior engineers - are pushing their own boulders, occasionally failing and watching them fall down the hill.&lt;/p&gt;

&lt;p&gt;This is what makes software engineering fun for me now; &lt;strong&gt;I recognize that it is a human process. Code is imperfect because we are imperfect.&lt;/strong&gt; Failing is just a part of the game. Everyone has failed before, and everyone will continue to fail. What makes this struggle all worth it is that every time we fail, we can hope to learn from these mistakes and make it just a little bit further up the hill before failing again.&lt;/p&gt;

&lt;p&gt;Writing imperfect code is okay as long as it works, because all code is imperfect. If it works, then that is better than what was there before. There will always be time to redesign and refactor. I love the quote that my manager(s) use occasionally when I fall into the trap of attempting to write perfect code; &lt;em&gt;"Chances are, what you expected to last forever will last a year, and the hack you put in will stay in production 'til after we die."&lt;/em&gt; We can't foresee the problems we'll encounter in the future, so we need to just take small steps forward up the mountain to get closer to the top.&lt;/p&gt;

&lt;p&gt;This means that instead of reaching for immediate perfection in my projects, I have started to force myself to build iteratively and progressively. Whereas I could have spent six months working on a project in my basement only to realize it sucks, I can spend one month writing a crappy project, release it, and spend the next five months getting valuable feedback and improving upon it slowly and iteratively. &lt;em&gt;That&lt;/em&gt; is software development.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Must Imagine the Programmer Happy
&lt;/h2&gt;

&lt;p&gt;One of Camus's strongest points is that &lt;strong&gt;we must imagine Sisyphus is happy pushing the rock up the hill&lt;/strong&gt;, because it is absurd to think otherwise. Since he knows that is all he can do, he gets enjoyment out of it, because there is no better alternative. Instead of seeing the rock as his punishment, he begins to see the challenge as his life purpose and the boulder as the vessel to accomplish that goal.&lt;/p&gt;

&lt;p&gt;As a software engineer, I take great pride in the code that I write. Even though I know most of it will be refactored, rewritten, or won't even be needed in a few years when the next thing comes around, it fills me with a happiness and purpose to know that for the meantime, my code is out there doing some really cool things. Past that, it doesn't matter for me. Even if I commit only a few lines of code to a project, that's better than nothing, and makes me a better developer than before.&lt;/p&gt;

&lt;p&gt;The challenges that present themselves in software development are not unlike Sisyphus pushing the rock up the hill. There are challenges, mistakes, and failures that can be represented by the boulder rolling down the hill. While this is true though, &lt;strong&gt;I always come back to programming. I always find joy in the challenges and the struggle.&lt;/strong&gt; Through the grunt work, the nitty-gritty details, and the imperfect solutions, I find meaning and solace that I am making a difference in the world.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>gratitude</category>
      <category>thoughts</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Time to Show Off the Robot</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Sat, 27 Apr 2019 02:16:19 +0000</pubDate>
      <link>https://dev.to/dan_starner/time-to-show-off-the-robot-30dp</link>
      <guid>https://dev.to/dan_starner/time-to-show-off-the-robot-30dp</guid>
      <description>&lt;p&gt;&lt;em&gt;Now, I usually don't do something like this, but I just spent most waking hours of my life the past 4 months doing nothing but robotics, so I thought I'd show off and be proud of my students...This also explains why I haven't posted since the end of December 😮 This post comes also almost word-for-word from when I wrote it on our team website at &lt;a href="https://www.ghouse354.com/2019-robot" rel="noopener noreferrer"&gt;https://www.ghouse354.com/2019-robot&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Back in December, I was looking for a new &lt;a href="https://www.firstinspires.org/robotics/frc" rel="noopener noreferrer"&gt;&lt;em&gt;FIRST&lt;/em&gt; Robotics&lt;/a&gt; team to work with. If you are not familiar with &lt;em&gt;FIRST&lt;/em&gt;, I highly recommend looking into it more and working with a team near you. The gist of it though is that high school teams spend six weeks building a 120 lbs robot that best accomplishes the game's goals. This year's game, &lt;strong&gt;Destination: Deep Space&lt;/strong&gt;, was all about moving Hatch Panels (poly-carbonate discs) and Cargo (rubber balls) around the field and placing them in their respective goal positions. You can watch the game video below.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Mew6G_og-PI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I ended up working with the &lt;a href="https://www.ghouse354.com/" rel="noopener noreferrer"&gt;G-House Pirates&lt;/a&gt;, who are located in Downtown Brooklyn in New York City. The students and the mentors acted like a close family, and by the end of the season, &lt;em&gt;I&lt;/em&gt; too felt like I had joined the family. So then, onto the exciting part...&lt;/p&gt;

&lt;h2&gt;
  
  
  The Robot, Ms Calculated
&lt;/h2&gt;

&lt;p&gt;Our robot for the 2019 FRC season, &lt;strong&gt;Ms Calculated&lt;/strong&gt;, was purpose-built to play FIRST: Destination Deep Space, presented by the Boeing Company. This robot is one of the most complex that the team has built with the current group of students and mentors. Again, &lt;strong&gt;we only had six weeks to build this&lt;/strong&gt;, I am so amazed at what the students accomplished, because the mentors only stepped in to offer advice. Everything was designed and built by the students.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvxahceirqb2gsgsjisdw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvxahceirqb2gsgsjisdw.jpg" alt="Ms Calculated at the Loading Station"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Ms Calculated had sensors and cameras to aid the drivers in collecting Hatch Panels and Cargo from the field’s Loading Station&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Using a two stage cascaded elevator design, along with a multi-purpose manipulator for collecting and depositing games pieces, Ms Calculated could score anywhere on the field, including at the Cargo Ship and all three levels of the Rocket with both Hatch Panels and Cargo balls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Robot Subsystems
&lt;/h3&gt;

&lt;p&gt;Our robot was very complex this year and involved many different moving pieces and subsystems, read more about them below!&lt;/p&gt;

&lt;h4&gt;
  
  
  Elevator Mechanism
&lt;/h4&gt;

&lt;p&gt;This was the first year our group of students and mentors attempted an elevator, and let’s just say that Ms Calculated could lift the game pieces to outer space! 🚀…or at least to the top of field’s Rockets! It featured the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A two stage cascaded elevator with &lt;a href="https://phoenix-documentation.readthedocs.io/en/latest/ch16_ClosedLoop.html#motion-magic-position-velocity-current-closed-loop-closed-loop" rel="noopener noreferrer"&gt;Motion Magic&lt;/a&gt; positioning using &lt;a href="http://www.ctr-electronics.com/talon-srx.html" rel="noopener noreferrer"&gt;Talon SRX speed controllers&lt;/a&gt; providing pin-point precision for lifting the manipulator to specific predetermined heights with little operator intervention&lt;/li&gt;
&lt;li&gt;A second stage containing the mechanism for manipulation field pieces&lt;/li&gt;
&lt;li&gt;Dual drive ropes for both lifting and lowering the stages, increasing the speed of the elevator &lt;/li&gt;
&lt;li&gt;Mounted Constant Force Springs on each stage allowing for less drive rope tension and required torque to lift the stages&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cargo Intake
&lt;/h4&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdla4xdf6vldrzthvw9fe.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdla4xdf6vldrzthvw9fe.gif" alt="Our ground to Rocket Cargo cycles were fast"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cargo manipulator was placed on the elevator and provided the ability to score anywhere on the field and at any Rocket level.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collection roller covered with a rubber coating allowed for collecting, holding, and depositing the Cargo &lt;/li&gt;
&lt;li&gt;Rotating &amp;amp; locking wrist allowed for both Loading Station and ground collection of Cargo&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Hatch Intake
&lt;/h4&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqxguwst93w4j0jn1b62i.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqxguwst93w4j0jn1b62i.gif" alt="Our hatch intake allowed for driver error and automated collection"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hatch mechanism extended over the bumpers using drawer-slides driven by pneumatics&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A pair of notched poly-carbonate pieces would extend once inside the Hatch Panel, holding it in place&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Later iterations, such as automatic collection using Limit Switches &amp;amp; hinged collection allowed for more driver error and faster scoring cycles&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Sandstorm / Autonomous Period
&lt;/h4&gt;

&lt;p&gt;The first 15 seconds of every match were played in Sandstorm, where black sheets were covering the Driver Stations, blocking any view of the robot(s) for the drivers, operators, and coaches. To account for this, Ms Calculated had the following hardware on board:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Limelight camera which could automatically find and follow the reflective tape above the Cargo Ship and Rocket. Using the outputted data, Ms Calculated could drive to and position herself into the scoring position&lt;/li&gt;
&lt;li&gt;A Pixy2 camera which could track and follow the lines in front of the scoring targets&lt;/li&gt;
&lt;li&gt;A 170° fish-eye camera which allowed the driver and operator to see what was around the robot to navigate the field&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffrerh8xcbrz3l43agshv.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffrerh8xcbrz3l43agshv.gif" alt="Our drivers were always prepared to handle the sandstorms"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Controls / Driver Station
&lt;/h4&gt;

&lt;p&gt;Not wanting to be outdone by the previous year’s Driver Station, the team decided to machine and build a truly unique and special Driver Station, which contained the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The driver station laptop and an external monitor, allowing the driver and operator to organize their cameras and telemetry&lt;/li&gt;
&lt;li&gt;Holds for the drive controllers&lt;/li&gt;
&lt;li&gt;Handles to make it easier to carry&lt;/li&gt;
&lt;li&gt;Custom team number and name engravings&lt;/li&gt;
&lt;li&gt;A custom-built LED screen and button board for controlling all mechanisms not associated with driving the robot, such as position selection elevator control using a potentiometer and execute button, hatch &amp;amp; cargo collection and deposition, and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out &lt;a href="https://imgur.com/gallery/Vii1UYP" rel="noopener noreferrer"&gt;https://imgur.com/gallery/Vii1UYP&lt;/a&gt; for some more images of our drive station!&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fsqkkz2a4286732lntbtk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fsqkkz2a4286732lntbtk.jpg" alt="Pretty Snazzy Drive Station"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  At the End of the Day
&lt;/h2&gt;

&lt;p&gt;We ended up getting eliminated in the closing playoff matches of each of our competitions, but at the end of the day, we still had an amazing year with a great robot and an even greater group of students...here's to 2020 being even better!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Oh, you think this is really cool and want to get involved? Lucky for you, there is &lt;a href="https://www.google.com/maps/d/u/0/viewer?mid=1sNr_BG6fmMVJjLovnQsevLDOfuE&amp;amp;ll=15.011038120855%2C0&amp;amp;z=3" rel="noopener noreferrer"&gt;probably a team in your area&lt;/a&gt;, so message me and I'll make sure to get you involved!... Especially if you're in the NYC area, we could always use more mentors!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>robotics</category>
      <category>frc</category>
      <category>hobbies</category>
    </item>
    <item>
      <title>Even the Big Ones Mess Up</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Thu, 27 Dec 2018 19:47:51 +0000</pubDate>
      <link>https://dev.to/dan_starner/even-the-big-ones-mess-up-51dp</link>
      <guid>https://dev.to/dan_starner/even-the-big-ones-mess-up-51dp</guid>
      <description>&lt;p&gt;This morning, my feed blew up due to my friends and family complaining about some new Instagram overhaul. Apparently, their feeds were scrolling horizontally instead of the usual vertically. It turns out that this was just one big &lt;em&gt;Oops&lt;/em&gt; on the part of Instagram's engineering team, as tweeted by their Head of Engineering.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1078321786586951680-785" src="https://platform.twitter.com/embed/Tweet.html?id=1078321786586951680"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1078321786586951680-785');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1078321786586951680&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;When stuff like this happens, it's funny to see how quickly the reactions both &lt;em&gt;for&lt;/em&gt; and &lt;em&gt;against&lt;/em&gt; the change propagate across social media, but in another way, it makes me feel better about being a developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  If They Can Mess Up, so Can You
&lt;/h2&gt;

&lt;p&gt;Stories like watching Instagram accidentally over-scale their testing of the new UI, or &lt;a href="https://gizmodo.com/alexa-crapped-out-on-christmas-1831319216" rel="noopener noreferrer"&gt;Amazon's Alexa crashing on Christmas due to the influx of new devices&lt;/a&gt; makes me realize that no matter how big or powerful these companies are, they are still run by humans, and humans miscalculate and make mistakes.&lt;/p&gt;

&lt;p&gt;So if Instagram or Amazon can make these mistakes, why do I give myself so much trouble for writing buggy code sometimes? No one can see all the use cases and outcomes of running their software, and mistakes do happen. &lt;/p&gt;

&lt;p&gt;You, me, Amazon or Instagram...we will never write &lt;em&gt;perfect&lt;/em&gt; software or always get things right, because there is no right way or perfect software. Whatever works for you, your team, or your company at the time is good enough until you have to make modifications for new user/edge cases.&lt;/p&gt;

&lt;p&gt;If we as developers kept programming until we thought our code was "perfect" then either it wouldn't &lt;em&gt;really&lt;/em&gt; be perfect, or we'd never finish it! Design and plan ahead as best as possible, but don't beat yourself up for writing buggy code, because it's natural and just happens. If there wasn't any buggy code or systems to fix, a lot of engineers would be out of jobs 🙈&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyo82s9f0n60jar741g0z.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyo82s9f0n60jar741g0z.jpg" alt="I will not make mistakes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No matter how much we prepare, we will make mistakes, that's just a part of life. What matters is how we face those mistakes and issues, and the tenacity we bring to making software better. These ideas scale, whether we are just solo developers, or are the big companies like Instagram.&lt;/p&gt;

&lt;p&gt;So what are your thoughts? How much fuss should we give companies this big when they make mistakes? What are your thoughts on writing code that walks the line between being good and being good &lt;em&gt;enough&lt;/em&gt;? What are your thoughts on the balance between tech debt and time to release?&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>career</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Lessons Learned: Python to JavaScript</title>
      <dc:creator>Daniel Starner</dc:creator>
      <pubDate>Wed, 12 Dec 2018 18:01:24 +0000</pubDate>
      <link>https://dev.to/dan_starner/lessons-learned-python-to-javascript-4lae</link>
      <guid>https://dev.to/dan_starner/lessons-learned-python-to-javascript-4lae</guid>
      <description>&lt;p&gt;&lt;em&gt;The following is just some things that I found interesting as I dive deeper into the world of JavaScript.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;At Bloomberg, I work with a DevOps / Infrastructure team that deals very heavily in hardware, networking, and scripting. This means that everyone on my team is very good with Bash, Ansible, and Python, and can probably rattle off more networking acronyms than seconds in a day.&lt;/p&gt;

&lt;p&gt;Shortly after I joined, we started to think about a web dashboard that would manage and automate many of our manual processes and tasks. We were planning all the features to include, and it was going to be &lt;em&gt;so cool&lt;/em&gt;...except that I was the only one on the team who knew anything about web development, and even that was a loose association. &lt;/p&gt;

&lt;p&gt;My previous experiences were writing backend services and APIs for web companies, but I really had no experience on the front end side of things. For this web dashboard project, I wanted to really dive in and learn React the right way, building out a full application on my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Below are just some things that I learned or find interesting about JavaScript, React, and front end development coming from a Python and backend standpoint. Note that I am building a React app, so many of these things revolve heavily around the library.&lt;/p&gt;

&lt;h3&gt;
  
  
  NPM Dev Dependencies vs Dependencies
&lt;/h3&gt;

&lt;p&gt;I didn't really understand this until I started building Docker images for my Express-React app. In Python, I would always have a &lt;code&gt;dev-requirements.txt&lt;/code&gt; file that contained my test and linting libraries and a &lt;code&gt;requirements.txt&lt;/code&gt; that held all other dependencies. Its really nice that both of these stay in &lt;code&gt;package.json&lt;/code&gt;, cleaning up the project structure and making it easier to automate installs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structure Doesn't Matter as Long as It Works
&lt;/h3&gt;

&lt;p&gt;When developing, I am constantly moving files and folders around, trying to achieve the best project structure. I always have the mindset of &lt;em&gt;Will this work if I add more stuff?&lt;/em&gt;, and it usually leads to a never ending rabbit hole of project structure management instead of coding.&lt;/p&gt;

&lt;p&gt;What I learned from different posts online, and from my own experience, React doesn't care what project structure you use, and so neither should you. Obviously, try to keep it clean and organized, but aside from that, if something works for you, don't bother refactoring it until you have to. I am a fan of my current folder structure that looks something like the following. Note that I omitted files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── __mocks__
├── bin                # Scripts needed to run Docker image
├── certs              # Any certs I need for local dev. These are mounted to container
├── dist               # Build directory
├── screenshots
├── src
│   ├── assets         # Non-JS or styling assets
│   │   ├── images
│   │   └── jss        # I use MaterialUI, which styles using JSS
│   ├── components     # General use components
│   │   └── hoc        # Higher order components
│   ├── config         # Configuration file for server that loads env to object
│   ├── core           # Anything that is crucial to the React app
│   │   ├── layout     # Core layout components
│   │   │   ├── header
│   │   │   └── sidebar
│   │   ├── pages      # Not Found / Landing pages
│   │   ├── state      # Core managed state, aka users and theme
│   │   │   ├── auth
│   │   │   └── meta
│   │   └── util       # Any usable themes
│   ├── pages          # List of top level React Router routes as 'pages'
│   ├── server         # All code pertaining to the Express server
│   └── tests          # All tests
└── webpack            # Webpack configs for server and client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  State Management / Cleaner Components
&lt;/h3&gt;

&lt;p&gt;State management in Python for me doesn't really exist, especially if its for more scripting purposes. I usually favor composition over inheritance, but it was always because of what I learned in school. Writing React components really made this idea stand out. &lt;/p&gt;

&lt;p&gt;Components are composed of smaller, possibly reusable components that each level in the hierarchy is responsible for rendering and/or maintaining a specific part of the application. It's a really cool feeling to reduce code lines because I recognized clever ways that components were either related, or could be composed of one another.&lt;/p&gt;

&lt;p&gt;The whole idea of the waterfall effect with props and state from parent to children components is really cool to see live, once you understand what is going on. This was something I didn't understand at first, but my code and relationships between components got much better as I understood proper state management.&lt;/p&gt;

&lt;h3&gt;
  
  
  Promises Are Super Confusing at First
&lt;/h3&gt;

&lt;p&gt;Coming from a synchronous Python / scripting world, JavaScript promises made &lt;strong&gt;zero sense&lt;/strong&gt; to me until about 3 days ago, so don't hate if my examples below are still bad. For the longest time I tried to make Promises synchronous, and I would be so confused at why things like the following returned a promise.&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;function&lt;/span&gt; &lt;span class="nf"&gt;fetchSomething&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;fetchURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/something&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="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="nx"&gt;fetchURL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// returns a promise&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Handling the axios call like a synchronous&lt;/span&gt;
&lt;span class="c1"&gt;// function leds to tons of horrible callback &lt;/span&gt;
&lt;span class="c1"&gt;// and uncaught promise exceptions 🤷🏼‍♂️&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSomethingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetchSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But now I understand the whole &lt;code&gt;then(callback)&lt;/code&gt;, &lt;code&gt;catch(errCallback)&lt;/code&gt; flow, and it makes &lt;em&gt;so much more sense&lt;/em&gt;. On the next iteration, I rewrote it as the following, which is a bit better:&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;function&lt;/span&gt; &lt;span class="nf"&gt;fetchSomething&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;fetchURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/something&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;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="nx"&gt;fetchURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Handling the axios call like a synchronous&lt;/span&gt;
&lt;span class="c1"&gt;// function leds to tons of horrible callback &lt;/span&gt;
&lt;span class="c1"&gt;// and uncaught promise exceptions 🤷🏼‍♂️&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSomethingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetchSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This version stopped many of those callback and promise exceptions. This also allowed my handler functions to expect the data already marshaled into my desired format by the fetch-ing functions. Finally, I started using &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt;. I'm still iffy on these, so I apologize if the following isn't 100% correct.&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchSomething&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;fetchURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/something&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Wait for the axios promise to resolve&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;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="nx"&gt;fetchURL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;err&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSomethingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetchSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm still actively learning more about this asynchronous workflow, but so far its pretty awesome.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus CSS: FlexBox is Amazing
&lt;/h3&gt;

&lt;p&gt;More of a CSS thing, but one of the major factors that kept me from getting into front end design sooner was dealing with element alignment and sizing. No matter what &lt;code&gt;margin&lt;/code&gt;s or &lt;code&gt;align&lt;/code&gt; values I put, nothing seemed to work. This was my first real experience playing with Flexbox in CSS3, and &lt;em&gt;OH SNAP&lt;/em&gt; it makes a world of a difference. Between &lt;code&gt;flex-grow&lt;/code&gt;, &lt;code&gt;flex-basis&lt;/code&gt;, &lt;code&gt;justify&lt;/code&gt;, and &lt;code&gt;align-items&lt;/code&gt;, positioning things in HTML is made a whole lot easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing Up
&lt;/h3&gt;

&lt;p&gt;Although there's a lot more stuff I could talk about, these were some of the more major experiences I've had with JavaScript, Express, and React over the past few months. I might write a second post in the future as my project matures. &lt;/p&gt;

&lt;p&gt;Thanks for reading 👍😃 &lt;/p&gt;

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