<?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: mkt</title>
    <description>The latest articles on DEV Community by mkt (@mktcode).</description>
    <link>https://dev.to/mktcode</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%2F649162%2F9a06faf2-cfde-486d-a11a-48a06bf7f7b9.png</url>
      <title>DEV Community: mkt</title>
      <link>https://dev.to/mktcode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mktcode"/>
    <language>en</language>
    <item>
      <title>Regular Expressions for Non-Programmers.</title>
      <dc:creator>mkt</dc:creator>
      <pubDate>Sat, 24 Jul 2021 10:21:43 +0000</pubDate>
      <link>https://dev.to/mktcode/regular-expressions-for-non-programmers-5dm2</link>
      <guid>https://dev.to/mktcode/regular-expressions-for-non-programmers-5dm2</guid>
      <description>&lt;h2&gt;
  
  
  Useful knowledge when working with long texts.
&lt;/h2&gt;

&lt;p&gt;This article is for those &lt;strong&gt;who don’t want to become an expert&lt;/strong&gt; but get the maximum reward with minimum effort. The internet is full of tutorials on the topic and there are already lots of really good resources. I will list some of them at the bottom. The “problem” I see is that they are mostly not really targeted at non-technical users. They try to explain everything in one article. You read the first few paragraphs and think: “Well… Some other day, maybe.”&lt;/p&gt;

&lt;p&gt;The goal of this article is that you can easily read it to the end, understand everything and go on with your life with an actual productivity gain. I will only cover a few handy things. Regular expressions can be very useful for anyone who is working with texts a lot and most editors support them, like all the popular office suites. I will use Google Docs for the examples in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Special Character
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bNmwFqAG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/700/0%2ApdndbPsLlaqfuO07.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bNmwFqAG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/700/0%2ApdndbPsLlaqfuO07.gif" alt="Example 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything behaves normally until we enable regular expressions. Suddenly not only the term “dog.” (with a dot at the end) is matched but also the first one, where there is no dot but a space. That’s because the dot has a special meaning in a regular expression. It’s like a placeholder that simply matches any character, even spaces and… yes, dots. Here’s another example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DEwDaAWt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/700/0%2AJe8RsnpLdycRA0Hs.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DEwDaAWt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/700/0%2AJe8RsnpLdycRA0Hs.gif" alt="Example 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the end, we search for any combination of three characters of which the last one is a “t”. Note how it also matches “ght” in “caught” and even “ It” because of the space character it starts with.&lt;/p&gt;

&lt;p&gt;This alone can already be quite useful in some situations but it certainly has its drawbacks. Most times, matching “anything” is not really what you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Special Characters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="//Placeholders%20a.k.a%20Character%20Sets"&gt; &lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;If you compare it to the dot and how it’s a placeholder for simply any character, you can say character sets are “custom placeholders” for only a few selected characters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YHEYtsBU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2ASZWgaDKcIMowHWp_wIi7Qg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YHEYtsBU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2ASZWgaDKcIMowHWp_wIi7Qg.png" alt="Example 3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This whole &lt;code&gt;[fcr]&lt;/code&gt; thing is now a placeholder for either an “f”, a “c” or an “r”. Combined with the “at” after it, this expression only matches exactly the three words “fat”, “cat” and “rat”. But, as you can see, &lt;strong&gt;also as part of other words&lt;/strong&gt;. You will learn how to avoid that in a moment.&lt;/p&gt;

&lt;p&gt;You can also define ranges of characters. To create a placeholder for any letter in the alphabet, you don’t need to write &lt;code&gt;[abcdefghijklmnopqrstuvwxyz]&lt;/code&gt;. You can simply write &lt;code&gt;[a-z]&lt;/code&gt;. For numbers it’s &lt;code&gt;[0-9]&lt;/code&gt; and you can even combine them easily. &lt;code&gt;[a-z0-9]&lt;/code&gt; is a placeholder for all letters and numbers and&lt;br&gt;
&lt;code&gt;[b-f1-6]&lt;/code&gt; is one for all letters from b to f and numbers from 1 to 6.&lt;/p&gt;

&lt;p&gt;Oh, and… In the first screenshot of this article, you see how “Match case” isn’t enabled. Otherwise, &lt;code&gt;[a-z]&lt;/code&gt; and &lt;code&gt;[A-Z]&lt;/code&gt; wouldn’t be the same. And in that case, don’t try things like &lt;code&gt;[A-z]&lt;/code&gt;. It doesn’t do what you might hope for. But you can use &lt;code&gt;[a-zA-Z]&lt;/code&gt;… or just check that box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GdeUtD06--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2APypuKz5zpVXqB699LoPEdQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GdeUtD06--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2APypuKz5zpVXqB699LoPEdQ.png" alt="Example 4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K-SbwYtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2ANzfrAmHp5Slr1o7XXvXuiw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K-SbwYtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2ANzfrAmHp5Slr1o7XXvXuiw.png" alt="Example 5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I guess this last example makes it very clear what regular expressions are all about. You aren’t bound to exact words or phrases. You can search a text for complex patterns. And this example also demonstrates how powerful that can be. How else would you search for… times?&lt;/p&gt;

&lt;h3&gt;
  
  
  ? * + (optional/repeating characters/placeholders)
&lt;/h3&gt;

&lt;p&gt;Sorry? Oh, yes. Sure. Just put a question mark after that first placeholder, to make the leading “0" optional while the hour is less than 10.&lt;/p&gt;

&lt;p&gt;Like asking yourself: “Is this really here… question mark”&lt;/p&gt;

&lt;p&gt;Those parenthesis? Good catch. You can group stuff together so that the question mark applies to it as a whole.&lt;/p&gt;

&lt;p&gt;Also handy: The plus sign and the star. &lt;code&gt;[0-9]+&lt;/code&gt; or &lt;code&gt;[a-z]*&lt;/code&gt;&lt;br&gt;
You can search for something that is there “at least one time” (plus) or “any number of times or not at all” (star). And if that is not enough, you can use { and } to say “two to four times”: &lt;code&gt;[0-9]{2,4}&lt;/code&gt; or “at least three times”:&lt;br&gt;
&lt;code&gt;[a-z]{3,}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J0EeZlB4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/700/0%2AprXEUpQGPmmgov7Z.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J0EeZlB4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/700/0%2AprXEUpQGPmmgov7Z.gif" alt="Example 6"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  \b (word boundary)
&lt;/h3&gt;

&lt;p&gt;Now back to the “also as part of other words” problem.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;\b&lt;/code&gt; “helper” doesn’t really match any characters. It means “word ends here” or “word starts here”, depending on where you put it. If you put it on both sides, that means you are looking for a “whole word”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CuDvDXZP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/700/0%2AZzX7H5ScF_WsrEqa.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CuDvDXZP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/700/0%2AZzX7H5ScF_WsrEqa.gif" alt="Example 7"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Problem solved.&lt;/p&gt;

&lt;h3&gt;
  
  
  | (this or that)
&lt;/h3&gt;

&lt;p&gt;The pipe character simply means “or”. You can basically search for multiple things at the same time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rHNbuzxj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2ASShkgMnarVXqDz6MMp7__Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rHNbuzxj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2ASShkgMnarVXqDz6MMp7__Q.png" alt="Example 8"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your “search options” can be as simple as single characters, like a|b, or more complex expressions. Let’s combine a few things here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r5uf-8FD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2A8734HKKIWY5gKG4rsvr1Zg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r5uf-8FD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2A8734HKKIWY5gKG4rsvr1Zg.png" alt="Example 9"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  \ (escaping)
&lt;/h3&gt;

&lt;p&gt;One last thing. So, there are special characters with a special meaning. By the way, these are all of them: &lt;code&gt;.+*?()[{^$|\&lt;/code&gt; That means you can’t just search for them literally. To do that you have to put a backslash in front of them. With that, we can fix the issue from the first example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Eh5ilC2B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/700/0%2AHYSHrQk6-31tzM_l.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Eh5ilC2B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://miro.medium.com/max/700/0%2AHYSHrQk6-31tzM_l.gif" alt="Example 10"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The end.
&lt;/h3&gt;

&lt;p&gt;We will stop here. I want this article to be “digestible” but that’s a lot of handy stuff already I believe. You can search for whole words only or for words that start or end with something or for multiple words, alternative/common (mis)spellings, patterns like time and date and more. If you want to explore the rabbit hole a bit more, there are some useful resources below.&lt;/p&gt;

&lt;p&gt;Other Resources&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://regextester.com"&gt;regextester.com&lt;/a&gt; and &lt;a href="https://regex101.com"&gt;regex101.com&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Awesome tools to build your own, more complex regular expressions. When hovering the expression field, it shows you what exactly is happening. They both also have a library of commonly used regular expressions which you can explore and try to make sense of.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.regular-expressions.info/quickstart.html"&gt;regular-expressions.info/quickstart.html&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Best quick start guide and cheat sheet but the visual style already scares you away. No need for regular expressions. Well… functional though.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=M7vDtxaD7ZU"&gt;https://www.youtube.com/watch?v=M7vDtxaD7ZU&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Great talk. Requires some experience follow along.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>regex</category>
    </item>
    <item>
      <title>Make GitHub Workflows run consecutively</title>
      <dc:creator>mkt</dc:creator>
      <pubDate>Fri, 16 Jul 2021 19:19:52 +0000</pubDate>
      <link>https://dev.to/mktcode/make-github-workflows-run-consecutively-265f</link>
      <guid>https://dev.to/mktcode/make-github-workflows-run-consecutively-265f</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR: &lt;a href="https://github.com/marketplace/actions/consecutive-workflow-action"&gt;https://github.com/marketplace/actions/consecutive-workflow-action&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article assumes some basic experience with GitHub Actions/Workflows.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you work with GitHub Actions, you have almost infinite flexibility at your disposal and you can do almost anything. But sometimes you have to be a bit creative to achieve what you want.&lt;/p&gt;

&lt;p&gt;Within a workflow, jobs are executed in parallel but you can create dependencies between jobs so that they run consecutively. Steps within a job always run one after another.&lt;/p&gt;

&lt;p&gt;Workflows themselves always run independently of each other. You can trigger the same workflow multiple times and there will be multiple parallel runs. That’s perfectly fine in many common scenarios. When there are two new pull requests, submitted at almost the same time, your automated tests can run for both pull requests at the same time.&lt;/p&gt;

&lt;p&gt;But there are situations where this actually leads to problems. I like to make my workflows not just perform some checks but actually update the repository. I use a repository kind of as a database. Automatically fixing code formatting issues is probably a more relatable use case. :D&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4e7cEdOl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2AC1klcgz1yev9baYgTn8A5A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4e7cEdOl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2AC1klcgz1yev9baYgTn8A5A.png" alt="Parallel Workflow Runs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem now with parallelism is of course this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Updates were rejected because the remote contains work that you do not have locally.
This is usually caused by another repository pushing to the same ref.You may want to first integrate the remote changes (e.g., 'git pull ...') before pushing again.
See the 'Note about fast-forwards' in 'git push --help' for details.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two workflow runs pushing to the same repository at almost the same time will most likely fail. Pulling changes right before pushing is the first thing that comes to mind but that can lead to merge conflicts that your workflow will not be able to resolve on its own.&lt;/p&gt;

&lt;p&gt;So you need to make sure that workflow run B only clones the repository once workflow run A is done with pushing. They need to run consecutively. GitHub does not offer that out of the box but a sufficiently comprehensive API to implement it yourself. I’ll not cover &lt;a href="https://github.com/mktcode/consecutive-workflow-action/blob/main/index.js"&gt;the implementation&lt;/a&gt; in detail but this is the general, fairly simple idea:&lt;/p&gt;

&lt;p&gt;As I mentioned before, you can create dependencies between jobs within a workflow. So the first job of the workflow is simply to look at its previous run and wait for it to complete. I don’t care about its result, whether it failed or succeeded or whatever, I just want to make sure it’s not running anymore. As long as that’s not the case, wait a bit and then check again. So there’s a nice little while loop involved to make sure this first job in our workflow blocks the rest of it until the previous run is completed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LT_kUzE6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2AQS0rXEVaLMvJKfTiWOUmJg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LT_kUzE6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2AQS0rXEVaLMvJKfTiWOUmJg.png" alt="Workflow-blocking job"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I just &lt;a href="https://github.com/marketplace/actions/consecutive-workflow-action"&gt;published this as a GitHub Action&lt;/a&gt; that is super easy to use, in case I’m not the only one with the problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;consecutiveness&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mktcode/consecutive-workflow-action@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;

  &lt;span class="c1"&gt;# your other jobs&lt;/span&gt;
  &lt;span class="na"&gt;something&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;consecutiveness&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One word about the &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;... The token is needed to avoid rate limitation issues when performing API calls. Make sure you read the &lt;a href="https://github.com/mktcode/consecutive-workflow-action#security-note"&gt;security note&lt;/a&gt; in the repository’s Readme.&lt;/p&gt;

&lt;p&gt;That’s it! Hope its useful for some. If you leave a like for the article you make me feel more comfortable about writing articles like this. Follow me on &lt;a href="https://github.com/mktcode"&gt;GitHub&lt;/a&gt; if you are interested in what I’m working on. Thanks for reading! :)&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Multiple Environments With GitHub Pages</title>
      <dc:creator>mkt</dc:creator>
      <pubDate>Fri, 25 Jun 2021 21:05:46 +0000</pubDate>
      <link>https://dev.to/mktcode/multiple-environments-with-github-pages-53me</link>
      <guid>https://dev.to/mktcode/multiple-environments-with-github-pages-53me</guid>
      <description>&lt;h1&gt;
  
  
  or... When you’re working on a static site and GitHub Pages feels like the perfect hosting solution, since you like having everything in one place and then you realize you need an additional environment but you still don’t want to use anything but GitHub… Here’s what you can do.
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AIN4wR0kIkrFKn6zg9aZgCA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AIN4wR0kIkrFKn6zg9aZgCA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is a “special needs” article but also a general introduction to GitHub Actions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since the advent of Single Page Applications the requirements for hosting have been reduced to a minimum. Anything that can serve a static HTML file will do the job just fine and the browser, bombarded with Javascript, does the heavy lifting. High availability and security is a totally different topic of course but in this case I believe GitHub has you covered.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://pages.github.com" rel="noopener noreferrer"&gt;Pages&lt;/a&gt; GitHub offers a very convenient service for hosting such an app. You push to your repository and GitHub updates the deployment for you. You even get a nice subdomain like &lt;code&gt;username.github.io&lt;/code&gt; or you can connect your own domain. There’s just one downside.&lt;/p&gt;

&lt;p&gt;If your project is not just a literally very static website but an actual “app” and you are working together with other people, you probably want to have multiple deployments, like a development and a staging environment. The problem is… A repository on GitHub can only have a single GitHub Page instance and you probably don’t want to maintain a mirror repository for each of your environments, so you’ll end up using an external hosting service and then you start questioning GitHub Pages as your preferred hosting solution all together. In this article I will show you what I did to “stay on GitHub“.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions &amp;amp; Workflows
&lt;/h3&gt;

&lt;p&gt;Not only does GitHub offer free hosting but also free and pretty flexible and powerful workflow automation with &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;. If you are not already familiar with CI/CD and GitHub Actions in particular, I recommend you to change that asap. I ignored this topic for far too long but now I’m a huge fan. It simply gives you superpowers.&lt;/p&gt;

&lt;p&gt;I’ll try to give you a brief but effective introduction:&lt;/p&gt;

&lt;p&gt;GitHub runs &lt;a href="https://github.com/actions/virtual-environments" rel="noopener noreferrer"&gt;virtual machines&lt;/a&gt; that act as task runners for your projects. Those tasks can either run on a schedule, as a cronjob, or get &lt;a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows" rel="noopener noreferrer"&gt;triggered by events&lt;/a&gt; that occur on GitHub, like pushing commits to a branch, opening or closing issues or commenting on them, to name some common ones. These tasks can be comprised of multiple steps that can be chained together and depend on one another, hence the term &lt;em&gt;Workflows&lt;/em&gt;. The terminology is the following: &lt;em&gt;Workflows&lt;/em&gt; have one or more &lt;em&gt;jobs&lt;/em&gt;, each with one or more &lt;em&gt;steps&lt;/em&gt;, which can make use of an &lt;em&gt;action&lt;/em&gt;, which can have &lt;em&gt;inputs&lt;/em&gt; and &lt;em&gt;outputs&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Actions are the “atoms” a workflow is made of, so to say. An action is a repository on GitHub, containing an &lt;code&gt;action.yml&lt;/code&gt; file, describing its inputs and outputs. That means an action can basically do anything you want. It is worth mentioning though, that you actually don’t even have to use actions in your workflows at all. You can also just run arbitrary commands on the operating system the workflow runs on and sometimes that is all you need. You can think of GitHub Workflows simply as… “executing stuff” on a virtual machine that GitHub spawns for you on demand.&lt;/p&gt;

&lt;p&gt;To add a new workflow you need to add a YAML configuration file inside the &lt;code&gt;.github/workflows&lt;/code&gt; directory of your repository. GitHub will automatically pick that up and run it according to the terms you configure. Here’s a &lt;code&gt;hello-world.yml&lt;/code&gt; that shows probably the most simple and useless workflow possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Hello World&lt;/span&gt;  
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;  
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;hello-world&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;  
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo Hello World!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(I’ll not cover the YAML format in this article but I’ll tell you that the dashes represent items in an array and if there are no dashes you are dealing with object keys. If there are dashes followed by… no dashes… it’s an array of objects. In contrast to plain JSON it supports single quotes and comments. Everything that is not quoted is considered a string, unless it’s obviously not a string.&lt;/em&gt; &lt;a href="http://yaml.org/spec/1.2/spec.html#id2759572" rel="noopener noreferrer"&gt;&lt;em&gt;YAML is a superset of JSON.&lt;/em&gt;&lt;/a&gt; &lt;em&gt;Wait… Did I just… anyway…)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once a day at 0:00 o’clock this workflow runs and prints “Hello World!” to some virtual Ubuntu machine’s &lt;em&gt;stdout&lt;/em&gt;, somewhere in GitHub’s networks. It does not use any action but instead runs an &lt;em&gt;echo&lt;/em&gt; command. A single step can either &lt;em&gt;run&lt;/em&gt; commands OR &lt;em&gt;use&lt;/em&gt; an action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;hello-world&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;  
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world/say-action@v1&lt;/span&gt;  
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
          &lt;span class="na"&gt;say&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Hello World!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You set the inputs of an action, if it has any, by using the &lt;em&gt;with&lt;/em&gt; keyword. I’d have called it &lt;em&gt;inputs&lt;/em&gt; but who cares.&lt;/p&gt;

&lt;h4&gt;
  
  
  Outputs and Dependent Jobs
&lt;/h4&gt;

&lt;p&gt;If an action has outputs, you can use them in consecutive steps of the same job. The step with the outputs just needs an &lt;em&gt;id&lt;/em&gt; to be referenced by other steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;hello-world&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;  
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get-name&lt;/span&gt;  
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world/get-name-action@v1&lt;/span&gt; &lt;span class="c1"&gt;# has a "name" output  &lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world/greet-action@v1&lt;/span&gt;          
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.get-name.outputs.name }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make a job dependent on a previous one and thus allow it to use its outputs, you must specify which outputs exactly to make available and then you define an array of &lt;em&gt;“needs”&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;get-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;  
    &lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.get-name.outputs.name }}&lt;/span&gt;  
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get-name&lt;/span&gt;  
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world/get-name-action@v1&lt;/span&gt; &lt;span class="c1"&gt;# has a "name" output&lt;/span&gt;

  &lt;span class="na"&gt;say-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;  
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world/greet-action@v1&lt;/span&gt;          
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.get-name.outputs.name }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Secrets &amp;amp; Environments
&lt;/h4&gt;

&lt;p&gt;Sometimes you need to use credentials, like an API key and you don’t want to expose such values in your workflow file directly. In your repository’s settings you’ll find a section called “Secrets”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2Ad9eZlMqTucjIdpCX3YmmVA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2Ad9eZlMqTucjIdpCX3YmmVA.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can define those values and you can use them in your workflows like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo ${{ secrets.SECRET_STRING }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secrets can also be defined for an entire organization on GitHub (go to its settings and there “Secrets”), to be available in all workflows across all repositories of that organization. On the other hand, you can also further restrict access to secrets. That’s what &lt;a href="https://docs.github.com/en/actions/reference/environments" rel="noopener noreferrer"&gt;environments&lt;/a&gt; are for. Think of them as “categories” of secrets in a certain repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AF3YUv1fb3_PqmH9TDTCVPg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AF3YUv1fb3_PqmH9TDTCVPg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idenvironment" rel="noopener noreferrer"&gt;tell a workflow job what environment’s secrets it can access&lt;/a&gt;. Let’s say you’ve created an environment in your repository’s settings called &lt;em&gt;development.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;hello-world&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;  
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;  
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo ${{ secrets.DEVELOPMENT_ENV_SECRET }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the environment settings you’ll see that access can also be restricted to certain branches, so that only a workflow that was triggered by an event related to a matching branch has access to the environment’s secrets and you can also require an admin to approve those workflow runs before they actually run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AAnVvdJJW9wW8aJFFtpg6BA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AAnVvdJJW9wW8aJFFtpg6BA.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2ARr1KzpVoi3PhippSgRvpRQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2ARr1KzpVoi3PhippSgRvpRQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll use that later in the article.&lt;/p&gt;

&lt;p&gt;After digesting this very condensed information you should have a feeling for how powerful and flexible these workflows can be and that you can basically do anything you can imagine. For example, I am using them to integrate cryptocurrency payments into deployment pipelines on GitHub but that’s a top secret project and you better erase that information from your memory right now. Ok? Good. Thanks.&lt;/p&gt;

&lt;p&gt;If you want to dive deeper into GitHub Actions on your own, browse through &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;the docs&lt;/a&gt; and if you want to start experimenting, I recommend adding &lt;a href="https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions" rel="noopener noreferrer"&gt;Workflow syntax for GitHub Actions&lt;/a&gt; and &lt;a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows" rel="noopener noreferrer"&gt;Events that trigger workflows&lt;/a&gt; to your bookmarks.&lt;/p&gt;
&lt;h4&gt;
  
  
  ATTENTION: A quick note on security when using secrets with unofficial/unverified actions!
&lt;/h4&gt;

&lt;p&gt;As mentioned, actions can take inputs. Those inputs can be secrets. A common example is Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;build-and-push-docker-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;  
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to DockerHub&lt;/span&gt;  
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v1&lt;/span&gt;   
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}&lt;/span&gt;  
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The action being used here is &lt;a href="https://github.com/marketplace/actions/docker-login" rel="noopener noreferrer"&gt;&lt;em&gt;docker/login-action&lt;/em&gt;&lt;/a&gt;&lt;em&gt;,&lt;/em&gt; which is a verified action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AZmdyb9cdwFHHuJM43YE9fQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2AZmdyb9cdwFHHuJM43YE9fQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What version of the action you use is specified by the &lt;code&gt;@v1&lt;/code&gt; at the end, which is a branch or tag name. Now, Docker might be a trustworthy author but even trusted organizations might have a new team member every now and then and sometimes new team members turn out to be not as trustworthy as the rest of the organization they just joined and security policies are sometimes more a theoretical thing. Anyway… At the latest when working with unverified actions you need to be aware of one thing (in case you aren’t already):&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;The code referenced by a tag can change!&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;That means: An action you pass one of your secrets to, can today be your best friend and tomorrow steal your most secret secrets, without you ever even knowing, and reveal them to your worst enemies, who are browsing the dark web looking for the latest hacks and leaks!&lt;/p&gt;

&lt;p&gt;If in any doubt, reference an action by a commit hash, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to shady service&lt;/span&gt;  
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trustme/spy-action@`172239021f7ba04fe7327647b213799853a9eb89`&lt;/span&gt;  
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SUPER_SECURE_PASSWORD }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;AND&lt;/strong&gt; make sure that the code referenced by that commit hash actually does what the readme says it does. If you don’t… well… then just use the tag name. You have been warned (by &lt;a href="https://docs.github.com/en/actions/learn-github-actions/finding-and-customizing-actions#using-release-management-for-your-custom-actions" rel="noopener noreferrer"&gt;the docs&lt;/a&gt; too).&lt;/p&gt;

&lt;h3&gt;
  
  
  My GitHub Pages Scenario
&lt;/h3&gt;

&lt;p&gt;Now that you should be up to speed with GitHub Actions and Workflows and everything, I’ll finally go into my specific real-world-scenario where a few workflows manage multiple GitHub Pages instances, representing different environments for the app I was working on.&lt;/p&gt;

&lt;p&gt;I needed three environments that behave like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Production&lt;/strong&gt;: The app that users will actually use. Updated on push to main branch, requiring admin approval.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Development&lt;/strong&gt;: Preview of the latest development progress. Updated on push to development branch.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Staging&lt;/strong&gt;: “Phoenix” deployments, created for pull requests from feature branches and deleted on close, which includes merges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main repository, just called “&lt;em&gt;app”&lt;/em&gt;, does not have it’s own GitHub Page. Instead there are two additional repositories, “&lt;em&gt;app-prod”&lt;/em&gt; and “&lt;em&gt;app-dev”&lt;/em&gt;. I mentioned in the beginning, that you surely don’t want to maintain any mirror repositories and that’s why these repositories only hold a build of the app and have their GitHub Page enabled. That’s their only purpose. Additionally each pull request on the development branch will result in a new repository named &lt;em&gt;“app-pr-”.&lt;/em&gt; Here’s a visualization:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2ApWeMVfOT5Hf15njwAHHLpw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2ApWeMVfOT5Hf15njwAHHLpw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Workflow: Development Build and Deploy
&lt;/h4&gt;

&lt;p&gt;Let’s start with the development deployment, since it’s the most straight forward without any extras. Take a look at &lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml" rel="noopener noreferrer"&gt;this workflow file&lt;/a&gt; and then I’ll guide you through it step by step. The gist of what happens is, we checkout the repository, build the app and push that build to the &lt;em&gt;app-dev&lt;/em&gt; repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.)&lt;/strong&gt; The workflow runs when new commits are pushed to the development branch but not for changes that only affect the workflow files themselves or any markdown files:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L2-L7" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L2-L7&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.)&lt;/strong&gt; We set the URL that is connected to the &lt;em&gt;app-dev&lt;/em&gt; repository’s GitHub Page as a global environment variable, to use it later in the workflow:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L8-L9" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L8-L9&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.)&lt;/strong&gt; There’s actually just one job, with a lot of steps, that runs on a &lt;em&gt;ubuntu-latest&lt;/em&gt; virtual machine, using secrets we configured for our development environment in the repository settings:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L11-L16" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L11-L16&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;FYI:&lt;/strong&gt; Setting the &lt;em&gt;url&lt;/em&gt; key for the environment only means that GitHub will show a link to that URL in different places on github.com, like in a related pull request or &lt;a href="https://github.com/OpenQDev/app/deployments" rel="noopener noreferrer"&gt;the repository’s deployments overview&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.)&lt;/strong&gt; We use &lt;a href="https://github.com/actions/setup-node" rel="noopener noreferrer"&gt;an official action&lt;/a&gt; (actions provided by the &lt;em&gt;actions&lt;/em&gt; GitHub organization) to prepare Node.js on the VM:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L18-L19" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L18-L19&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.)&lt;/strong&gt; We use normal Git commands to set the GitHub Actions bot as the commit author, because later we will commit and push changes to a repository:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L22-L25" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L22-L25&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.)&lt;/strong&gt; We checkout the repository to a &lt;em&gt;build&lt;/em&gt; directory, using &lt;a href="https://github.com/actions/checkout" rel="noopener noreferrer"&gt;the official checkout action&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L27-L30" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L27-L30&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;FYI:&lt;/strong&gt; If you don’t pass a specific repository name to the checkout action, like in step 8, it will simply checkout the repository in which the workflow lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.)&lt;/strong&gt; We move into this &lt;em&gt;build&lt;/em&gt; directory and actually build the app (a &lt;a href="https://nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt.js&lt;/a&gt; app by the way), after setting some environment variables. Then we move back to the parent directory:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L32-L41" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L32-L41&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;FYI:&lt;/strong&gt; Exported environment variables are not persistent across jobs/steps and are not to be confused with the &lt;a href="https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#env" rel="noopener noreferrer"&gt;workflow’s env vars&lt;/a&gt; (line 9), which are available throughout the entire workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8.)&lt;/strong&gt; Now we checkout the &lt;em&gt;app-dev&lt;/em&gt; repository to a &lt;em&gt;deploy&lt;/em&gt; directory, this time also providing a personal access token as a secret. This allows as to push changes to that repository in the next step:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L43-L48" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L43-L48&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9.)&lt;/strong&gt; And then we simply copy the files from the &lt;em&gt;build&lt;/em&gt; directory to the &lt;em&gt;deploy&lt;/em&gt; directory, resulting in changes in the repository that we then need to commit and push:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L50-L58" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L50-L58&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;FYI:&lt;/strong&gt; &lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-dev.yml#L54-L55" rel="noopener noreferrer"&gt;Line 54 and 55&lt;/a&gt; add files needed for the GitHub Page to work properly. We disable Jekyll as GitHub’s default static site generator (we take care of that ourselves by using Nuxt) and we configure the domain we want to be connected to our development deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Done!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now isn’t that super easy and intuitive? :D I’m not claiming that this is the smartest and most efficient way of doing this. But I hope it’s comprehensible enough. Let’s move on to the production deployment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Workflow: Production Build and Deploy
&lt;/h4&gt;

&lt;p&gt;Take a look at &lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-prod.yml" rel="noopener noreferrer"&gt;the workflow file&lt;/a&gt; and you will notice that it’s… pretty much the same. The only differences are &lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-prod.yml#L4" rel="noopener noreferrer"&gt;the branch this workflow “listens” to&lt;/a&gt;, &lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-prod.yml#L9" rel="noopener noreferrer"&gt;the deployment URL&lt;/a&gt;, &lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-prod.yml#L15" rel="noopener noreferrer"&gt;the environment&lt;/a&gt;, two of &lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-prod.yml#L37-L38" rel="noopener noreferrer"&gt;the env vars used when building the app&lt;/a&gt; and &lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-prod.yml#L46" rel="noopener noreferrer"&gt;the deployment repository&lt;/a&gt;. The more significant difference however, is the environment’s configuration in the repository settings. It will allow this workflow to run only after an admin approved it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2A3n785G9UwGbmdJIzPmNYNg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2A3n785G9UwGbmdJIzPmNYNg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And I think that’s all there is to say about the production deployment and we can take a look at the most interesting part of all this. Pull requests.&lt;/p&gt;

&lt;h4&gt;
  
  
  Workflow: Pull Request Build and Deploy
&lt;/h4&gt;

&lt;p&gt;Again, first go through &lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-pr.yml" rel="noopener noreferrer"&gt;the workflow file&lt;/a&gt;, try to make sense of it on your own and then I’ll just explain what’s different here.&lt;/p&gt;

&lt;p&gt;Most importantly there is no repository for this deployment yet. We have to create it from our workflow. To have a unique name for the repository, we fetch the GraphQL ID of the pull request:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-pr.yml#L9-L10" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-pr.yml#L9-L10&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;FYI:&lt;/strong&gt; The &lt;code&gt;github&lt;/code&gt; variable lets you access the context of a workflow run, e.g. the event that triggered it, including the pull request object itself.&lt;/p&gt;

&lt;p&gt;There’s a separate job that creates the repository, using a special action I created:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-pr.yml#L13-L21" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-pr.yml#L13-L21&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can take a look at the action itself, to see what’s actually going on:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/mktcode/create-repository-action/blob/b1dd3b3dcdcc491795ae189db97383a47f04808e/index.js#L6-L29" rel="noopener noreferrer"&gt;https://github.com/mktcode/create-repository-action/blob/b1dd3b3dcdcc491795ae189db97383a47f04808e/index.js#L6-L29&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next job depends on the repository being created and uses the &lt;em&gt;pr-staging&lt;/em&gt; environment:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-pr.yml#L23-L28" rel="noopener noreferrer"&gt;https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/deploy-pr.yml#L23-L28&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From there on it’s pretty much the same as the development and production workflow. The only thing missing is deleting the repository, once the pull request gets merged/closed. This is handled in &lt;a href="https://github.com/OpenQDev/app/blob/715573cd6ceebe87e7e235180510eaacacb7d74a/.github/workflows/delete-deploy-pr.yml" rel="noopener noreferrer"&gt;a separate workflow&lt;/a&gt;. It uses &lt;a href="https://github.com/mktcode/delete-repository-action/blob/8e1765df0893c4555badce772bcd0b732e2770e3/index.js#L6-L17" rel="noopener noreferrer"&gt;another special action&lt;/a&gt; I created.&lt;/p&gt;

&lt;p&gt;An now you can have pull requests &lt;a href="https://github.com/OpenQDev/app/pull/134" rel="noopener noreferrer"&gt;like this one&lt;/a&gt; with their own automatic deployments to test the changed before merging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2A9KXetmxMVHCfxfNiTwFxpQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F800%2F1%2A9KXetmxMVHCfxfNiTwFxpQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And that’s actually it! We’re done.&lt;/strong&gt; We now have a static site project, with multiple environments, living entirely on GitHub. And this is just one possible configuration of which I’m sure is far from perfect. In fact I am working on some improvements. So maybe I will update this article soon. But I hope you got a feeling for what’s possible with GitHub Actions and Workflows and that you start experimenting and creating your own ones for your own individual purposes now.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Thanks for reading!&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;You can follow me on&lt;/em&gt; &lt;a href="https://twitter.com/@thecodelander" rel="noopener noreferrer"&gt;&lt;em&gt;Twitter&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and&lt;/em&gt; &lt;a href="https://github.com/mktcode" rel="noopener noreferrer"&gt;&lt;em&gt;GitHub&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. For longer discussions, questions, feedback and so on, I just created a&lt;/em&gt; &lt;a href="https://discord.gg/vnGDEg9Ydv" rel="noopener noreferrer"&gt;&lt;em&gt;Discord Server&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. Not sure if that really makes sense but feel free to step by and say hello. :)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>githubpages</category>
      <category>githubactions</category>
      <category>programming</category>
    </item>
    <item>
      <title>GitHub Pages with Dynamic Routes</title>
      <dc:creator>mkt</dc:creator>
      <pubDate>Fri, 18 Jun 2021 10:29:05 +0000</pubDate>
      <link>https://dev.to/mktcode/github-pages-with-dynamic-routes-dli</link>
      <guid>https://dev.to/mktcode/github-pages-with-dynamic-routes-dli</guid>
      <description>&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%2Fxo5csn857fnxelq77xa3.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%2Fxo5csn857fnxelq77xa3.png" alt="GitHub Pages Dynamic Routes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The problem covered in this article has been discussed for some time now: &lt;a href="https://github.com/isaacs/github/issues/408" rel="noopener noreferrer"&gt;https://github.com/isaacs/github/issues/408&lt;/a&gt; Unfortunately there’s still no real solution and a workaround is needed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pages.github.com" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt; is a super convenient hosting service for static sites, e.g. a personal portfolio or blog or a project’s documentation and even modern web apps are in many cases not much more than a static HTML file and (a lot of) Javascript. But static sites come with the downside of... well... being static. That means you can’t have dynamic routes, like &lt;code&gt;your-project.github.io/posts/&amp;lt;post-slug&amp;gt;&lt;/code&gt; where &lt;code&gt;&amp;lt;post-slug&amp;gt;&lt;/code&gt; is a dynamic parameter. All possible routes need to be known in advance and point to a static file. Maybe those files are generated by some build process and whenever you add a new blog post, you just re-deploy the page. Using CI/CD pipelines like GitHub Actions/Workflows, that process might even boil down to pushing a new markdown file to your repository and that is sufficiently convenient for a lot of scenarios. But sometimes it’s not and you just need dynamic paths, especially when user generated content is involved or a project becomes more complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do dynamic routes even work?
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;If you are well familiar with the concept and you just want to know how to trick GitHub Pages into supporting dynamic routes, you can skip this part and continue with The Solution.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A route/path is traditionally pointing to a (static) file on the server that is represented by a domain.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;your-server.com/some/path/index.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you try to access a file that does not exist on that server, it will respond with an error, which usually means it will serve you a default &lt;code&gt;404.html&lt;/code&gt; that comes with the server. You’ve probably seen something like this:&lt;/p&gt;

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

&lt;p&gt;That’s such a default error file, in this case served by an nginx server. However, you can configure a server in a way that it serves you a certain resource/file, no matter what path you request. Let’s say you have an &lt;code&gt;index.html&lt;/code&gt; on the server and you configured it accordingly. You can now call &lt;code&gt;your-server.com/index.html&lt;/code&gt; but also &lt;code&gt;your-server.com/some/path/that/does-not-exist.html&lt;/code&gt;. It will always return the same &lt;code&gt;index.html&lt;/code&gt; file. Now, that file can also be a script instead of just some static HTML file. Otherwise your dynamic routes wouldn’t be that dynamic since they all serve exactly the same content.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Front_controller" rel="noopener noreferrer"&gt;front controller&lt;/a&gt; is such a dynamic script that handles all requests to your server and serves content dynamically, e.g. by fetching data from a database, based on what the actual request was, and then generating an HTML response from a template. This way you can support routes with dynamic parts, that you don’t have to know in advance, like the &lt;code&gt;/posts/&amp;lt;post-slug&amp;gt;&lt;/code&gt; example from above.&lt;br&gt;
GitHub Pages&lt;/p&gt;

&lt;p&gt;GitHub Pages does not support such a front controller because it is not meant to serve dynamic content. Sure. You can use Javascript in your static HTML files to change its content dynamically, e.g. based on user interaction, and most web apps, as mentioned before, are nothing more than a static HTML file and then Javascript takes over from there. But all this happens in your browser and not on GitHub’s servers. So if you call &lt;code&gt;your-username.github.io/some/file.html&lt;/code&gt; it will look for exactly that file and nothing else and if it can’t find it, because you didn’t add it to your repository, it will show you this:&lt;/p&gt;

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

&lt;p&gt;That is GitHub’s default &lt;code&gt;404.html&lt;/code&gt; file. At this point I assume that most developers/users would now simply accept the bitter reality that GitHub Pages might not be the right service for them and instead move on to a more comprehensive hosting solution, where they have more control over what the server actually does behind the scenes. But not me! I’m a lazy minimalist and one platform account must be enough!&lt;/p&gt;
&lt;h3&gt;
  
  
  An old-fashioned alternative
&lt;/h3&gt;

&lt;p&gt;At first I considered just being fine with a compromise and instead of having “real” dynamic routes, I could go back in the history of single page apps and use the &lt;code&gt;#&lt;/code&gt; method, &lt;a href="https://www.w3schools.com/angular/tryit.asp?filename=try_ng_routing" rel="noopener noreferrer"&gt;like the old AngularJS&lt;/a&gt;. In case you ever wondered, the part after the &lt;code&gt;#&lt;/code&gt; is not really part of the actual URL a server responds to. It is just used by the browser to jump to an HTML anchor and you can access it in Javascript. The server does not even know about this part. It’s client-side only. But that means you can have routes that look like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;your-server.com/#/posts/&amp;lt;post-id&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The app lives at &lt;code&gt;/&lt;/code&gt; on the server and processes the part after &lt;code&gt;#&lt;/code&gt; when running in the browser. When clicking a link in your app, it just updates the part after &lt;code&gt;#&lt;/code&gt; and changes the content accordingly via Javascript. But that doesn’t look that nice and modern frameworks, like Next.js do not even support this form of routing anymore. &lt;a href="https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-router#fallback" rel="noopener noreferrer"&gt;Vue’s Nuxt.js actually has a fallback option&lt;/a&gt; but still...&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;You already saw part of the solution in this post. It’s the 404 page. GitHub Pages actually allows you to add a custom &lt;code&gt;404.html&lt;/code&gt; to your repository, to adjust it to your project’s branding and so on. If you are familiar with the front controller pattern, you might have an “aahhhhhaaaa” moment now. The important part of this pattern is that it just takes any request and routes it to your app where the request is then handled. Well.. a 404 page is not much different. It handles aaaaall requests... that do not match any existing resource. You know where I am going with this? It is a bit different though. A classical front controller lives on the server and sends the desired response back to you as if the resource you requested actually exists physically. Tricking GitHub Pages into supporting dynamic routes is a bit more... tricky. Because it simply doesn’t! But we can make it look as if it does. The average human eye won’t notice the difference and it even works with the dynamic routing features of modern frameworks like Vue’s Nuxt.js or React’s Next.js.&lt;/p&gt;
&lt;h3&gt;
  
  
  Proof of concept
&lt;/h3&gt;

&lt;p&gt;The simple trick is to let your custom &lt;code&gt;404.html&lt;/code&gt; redirect any request back to your app and then your app uses the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API" rel="noopener noreferrer"&gt;browser history API&lt;/a&gt; to update the URL that your browser shows, to whatever was requested originally. You need to pass that information to your app when redirecting. For that I use... guess what?... our old friend, the &lt;code&gt;#&lt;/code&gt;. I have a GitHub page set up here: &lt;a href="https://mktcode.github.io/static-dynamic-routing/" rel="noopener noreferrer"&gt;https://mktcode.github.io/static-dynamic-routing&lt;/a&gt; and its &lt;code&gt;404.html&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&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="s1"&gt;/static-dynamic-routing/#&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&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="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="s1"&gt;/static-dynamic-routing&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So instead of showing some 404 Not found! message, it just redirects you to the root path where the app lives. Note that this GitHub Page example lives in the subdirectory &lt;code&gt;/static-dynamic-routing/&lt;/code&gt; which is normal, when you set up a GitHub page for a repository. It will live under &lt;code&gt;&amp;lt;your-username&amp;gt;.github.io/&amp;lt;repo-name&amp;gt;/&lt;/code&gt;. That’s why we have to do some replacements here. Otherwise we’d redirect the user to &lt;code&gt;mktcode.github.io/&lt;/code&gt;. Fortunately you can &lt;a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site" rel="noopener noreferrer"&gt;configure a custom domain for your GitHub Page&lt;/a&gt; very easily and then you don’t have to take care of this.&lt;/p&gt;

&lt;p&gt;So now, no matter what route we call, we’ll end up at our app and it will know about that route, so it can act accordingly. In my little example I do not much more than replacing the displayed URL in the address bar and manipulating some content. That’s basically how dynamic routing works in those modern frameworks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My App&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My App&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Post Path: {PATH}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&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;1&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&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="s1"&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="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pushState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Some title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/static-dynamic-routing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&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="s1"&gt;{PATH}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try opening this link in your browser: &lt;a href="https://mktcode.github.io/static-dynamic-routing/posts/my-post" rel="noopener noreferrer"&gt;https://mktcode.github.io/static-dynamic-routing/posts/my-post&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a split second you can see the &lt;code&gt;#&lt;/code&gt; in your browser’s address bar. That’s when the redirect happens and then we pretend it never did happen. And that’s basically all there is to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use with Nuxt.js
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;If you are more the react type of person, you’ll have to implement that on your own. I’ll only show the Nuxt.js way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nuxtjs.org/docs/2.x/features/file-system-routing#dynamic-routes" rel="noopener noreferrer"&gt;In Nuxt.js you can easily configure dynamic routes&lt;/a&gt; by just creating a file like &lt;code&gt;/pages/posts/_slug.vue&lt;/code&gt;. Nuxt will do the rest and you have routes like &lt;code&gt;/posts/my-post-title&lt;/code&gt;. This even works in static site mode but only if the site is delivered by the integrated Nuxt server or any other server configured in the same way (think: front controller pattern). With GitHub Pages this does not work and you’ll just see the 404 page. But here’s the proof that my approach works totally fine even with Nuxt.js:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mktcode.github.io/dynamic-nuxt-gh-pages/post/my-totally-dynamic-post-title" rel="noopener noreferrer"&gt;https://mktcode.github.io/dynamic-nuxt-gh-pages/post/my-totally-dynamic-post-title&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All it needs is &lt;a href="https://github.com/mktcode/dynamic-nuxt-gh-pages/blob/main/static/404.html" rel="noopener noreferrer"&gt;&lt;code&gt;the 404.html&lt;/code&gt; file in the &lt;code&gt;static&lt;/code&gt; directory&lt;/a&gt; and a &lt;a href="https://github.com/mktcode/dynamic-nuxt-gh-pages/blob/main/middleware/gh-pages-dynamic-routes.js" rel="noopener noreferrer"&gt;router middleware&lt;/a&gt;, which performs a nuxt-internal redirect to the original route, resulting in the address bar of your browser being updated. If that route does not exist in your application, the &lt;a href="https://nuxtjs.org/docs/2.x/concepts/views/#error-page" rel="noopener noreferrer"&gt;Nuxt error page&lt;/a&gt; shows. By the way... It now uses &lt;code&gt;#!&lt;/code&gt; for the redirect, to still allow normal HTML anchors. Everything that worked before should still work, plus... dynamic routes for GitHub Pages! Well... kind of. :)&lt;/p&gt;

&lt;h3&gt;
  
  
  The End
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Hope you enjoyed my first ever article! :) Follow me on &lt;a href="https://twitter.com/@thecodelander" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and &lt;a href="https://github.com/mktcode" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and comment and bla bla bla. There’s more to come! Five years later... “Hey I think I’ll start writing dev articles!” :D&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
