Jamstack sites have taken the world by storm, as evidenced by the fierce competition between offerings such as Netlify and Cloudflare. Azure recently threw its hat into this highly competitive ring with its new Static Web Apps (SWAs) offering.
In this tutorial, we’ll show you how to work with Static Web Apps and demonstrate how to deploy one using GitHub Actions.
Jamstack stands for JavaScript, API and Markup. In Jamstack websites, the application logic typically resides on the client side. These clients are most often built as single-page applications and often have HTML files statically generated for every possible path to support search engine optimization.
Azure Static Web Apps was released for general use in May 2021. Its features include:
Significantly, these features are available to use for free. With Netlify, there is also a free tier; however, it’s quite easy to exceed the build limits of the free tier and land yourself with an unexpected bill. By combining Azure Static Web Apps with GitHub Actions, we can build comparable experiences and save ourselves money!
So let’s build ourselves a simple SWA and deploy it with GitHub Actions.
Inside the root of our repository, we’re going to create a Docusaurus site. Docusaurus is a good example of a static site, the kind that is a natural fit for Jamstack. We could also use something else, such as Hugo.
At the command line, we’ll enter:
npx create-docusaurus@latest website classic
This will cause Docusaurus to create a new site in the website
directory.
Let’s commit and push this and turn our attention to Azure.
There’s a number of ways to create a Static Web App in Azure. It’s possible to use infrastructure as code with a language such as Bicep. But for this tutorial, we’ll use the Azure Portal instead.
If you don’t have an account already, you can set one up for free very quickly.
Once you’ve logged in, click Create a resource and look up Static Web App:
Click Create and you’ll be take to the creation dialog:
You’ll need to create a resource group for your SWA to live in. Give the app a name, choose the free plan, and enter GitHub as the deployment source.
Click the Sign in with GitHub button and authorize Azure to access your GitHub account for Static Web Apps.
At this point, Azure will query GitHub on your behalf and look up the organizations and repositories you have access to. Select the repository to which you’d like to deploy to your Static Web App and select the branch you’d like to deploy.
You also need to provide Azure with some build details to help it understand how your app is built. We’ll provide a preset of Custom. We’ll set the App location (the root of our frontend app) to be "/website"
to tally up with the application we just created.
We’ll leave API location blank and set the output location to be "build"
— this is the directory under website
where Docusaurus will create our site.
Finally, click Review + create and then Create.
Azure will now:
Pretty amazing, right?
When you look at the resource in Azure, it will look something like this:
If you click on the GitHub Action runs, you’ll be presented with your GitHub Action:
When that finishes running, you’ll be able to see your deployed Static Web App by clicking on the URL in the Azure Portal:
We’ve gone from having nothing to having a brand new website in Azure, shipped via continuous deployment in GitHub Actions in a matter of minutes. This is low-friction and high-value!
Now that we’ve done our initial deployment, let’s take it a step further and add authentication.
One of the great things about Static Web Apps is that authentication is available out of the box. We can pick from GitHub, Azure Active Directory, and Twitter as identity providers.
Let’s roll with GitHub and amend our website/src/pages/index.js
to support authentication:
import React, { useState, useEffect } from 'react'; import clsx from 'clsx'; import Layout from '@theme/Layout'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import styles from './index.module.css'; /** * @typedef {object} UserInfo * @prop {"github"} identityProvider * @prop {string} userId * @prop {string} userDetails * @prop {string[]} userRoles */ /** * @return {UserInfo | null} */ function useUserInfo() { const [userInfo, setUserInfo] = useState(null); useEffect(() => { async function getUserInfo() { const response = await fetch('/.auth/me'); const payload = await response.json(); const { clientPrincipal } = payload; return clientPrincipal; } getUserInfo().then((ui) => setUserInfo(ui)); }, []); return userInfo; } export default function Home() { const { siteConfig } = useDocusaurusContext(); const userInfo = useUserInfo(); return ( <Layout title={`Hello from ${siteConfig.title}`} description="Description will go into a meta tag in <head />" > <header className={clsx('hero hero--primary', styles.heroBanner)}> <div className="container"> <h1 className="hero__title">{siteConfig.title}</h1> <p className="hero__subtitle">{siteConfig.tagline}</p> <div className={styles.buttons}> {userInfo ? ( <p>Hello {userInfo.userDetails}</p> ) : ( <a className="button button--secondary button--lg" href="/.auth/login/github" > Click here to login </a> )} </div> </div> </header> </Layout> ); }
The above code does the following:
useUserInfo
, which calls the /.auth/me
endpoint of your SWA. This returns null
when not authenticated and the UserInfo
when authenticated/.auth/login/github
, thus triggering the GitHub authentication flowuserDetails
, the GitHub usernameLet’s commit and push this and, when our build has finished running, browse to our Static Web App once again:
If we click to login, we’re taken through the GitHub authentication flow:
Once you’ve authorized and granted consent, you’ll be redirected to your app and see that you’re logged in:
If we pop open the devtools of Chrome, we’ll see what comes back from the /.auth/me
endpoint:
{ "clientPrincipal": { "identityProvider": "github", "userId": "1f5b4b7de7d445e29dd6188bcc7ee052", "userDetails": "johnnyreilly", "userRoles": ["anonymous", "authenticated"] } }
We’ve now implemented and demonstrated authentication with Azure Static Web Apps with very little effort. This is tremendous!
Finally, let’s look at a super cool feature that Static Web Apps provides by default. If you take a look at the Environments tab of your SWA you’ll see this:
Open pull requests against the linked repository to create a staging environment.
Let’s try that out! We’ll create a new branch:
git checkout -b feat/show-me-staging
In our index.js
, we’ll add an arbitrary piece of text:
<p>I'm a staging environment!</p>
Then, we’ll commit and push our branch to GitHub and create a pull request. This triggers our GitHub Action to run once again.
Time, rather than publishing over our existing Static Web App, it’s going to spin up a brand new one with our changes in. It will also display a link for us in our GitHub pull request so we can browse straight to it:
This is the equivalent of Netlify Deploy Previews, implemented with Azure Static Web Apps and GitHub Actions. The allowance for GitHub Actions currently sits at 2,000 free minutes per month, compared to Netlify’s 300 free minutes per month, so you’re much less likely to receive a bill for using Static Web Apps.
This staging environment will last only until the pull request is closed. At that point, the environment is torn down by the GitHub Action.
In this tutorial, we deployed a website to a Static Web App using GitHub Actions and implemented authentication. We also demonstrated Azure’s equivalent to Netlify’s deploy previews and how to set up staging environments.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.