11ty data modeling with KITTENS!
05 Nov 2020
On StackOverflow, Drupal user Alec asked how dynamic “list” generation worked in 11ty compared to Drupal views. I know nothing about Drupal, but I thought it’d be a good time to dump a lot of knowledge I’ve learned this year about data modeling in the Eleventy static site generator.
##
First, a brief aside about Drupal vs. 11ty
Drupal, like Wordpress, is a web site generation tool whose content is stored in a [relational (SQL) database management system(]https://en.wikipedia.org/wiki/Relational_database#RDBMS){:target=”_blank”}.
By contrast, 11ty is meant to generate web sites from content either:
- stored in ordinary computer files, or
- stored elsewhere on the internet, but that would make a perfectly good computer file, and that is retrievable by executing some code written in Node.js-style JavaScript
The biggest difference, though, is not so much where the data is stored but the shape in the site generation tool sees it.
Go have a look at my explanation of table-shaped vs. nested-list-shaped data to get some sense of the difference.
Not explained in that post, however, is the idea of combining multiple table-shaped data files by including identifying data from one table inside the details of another and indicating that it represents a link into a row of some other table. (That’s what is meant by “relational” in “relational database.”)
Today, we’re going to work with a really simple nested-list-shaped dataset for 11ty:
- (kitten #1)
- name: Oreo
- colors:
- black
- white
- slug: oreo
- (kitten #2)
- name: Hershey
- colors:
- brown
- slug: hershey
In a relational database, your data might look something more like these 3 tables:
kittens
slug | name |
---|---|
oreo | Oreo |
hershey | Hershey |
valid_colors
row_id | color |
---|---|
1 | black |
2 | brown |
3 | white |
4 | gray |
5 | orange |
6 | tan |
kitten_coloration
kitten_slug | color_id |
---|---|
oreo | 1 |
oreo | 3 |
hershey | 2 |
From what I can tell, it looks like relational-database-aware web site building systems like Drupal can make it relatively easy to build a URL like https://mysite.com/black/
that lists, perhaps, not only all black kittens, but all black dogs stored in a dogs table of your database (not shown).
Building a similar type of URL called https://mysite.com/black/
using a site building system like 11ty where data is stored in
Previously, I designed a data structure in the “front matter” of Markdown files that was ready to be “dragged around” a content management system – and hence a web page – by a content author, providing a Squarespace-like experience.
I’ve spent enough time dinking around in Sanity CMS that I’m ready to share how I built the same data model inside of it.
End goal HTML
As before, I’m trying to build a home page that looks like this:
Header placeholderI did it!✓ eat wellX sleep soundly✓ jump high✓ write✓ hydrate regularlyHello WorldFooter placeholder
However, I want it to be easy for an author to edit and re-order, and even add sections so that perhaps they look like this:
Header placeholderHello beautiful world.Wow, I did it!✓ stretch gracefully✓ eat wellX writeX sleep soundlyX jump high✓ hydrate regularlyI'm proud of me.Footer placeholder
Page data structure
Conceptual
This is essentially the user interface that many content management systems (CMSes) are in the business of providing to web site content authors.
Designing content for web pages as a list of sections, each of which might just have simple details (a.k.a. “properties” or “attributes”), or which might contain further lists of subsections / items, is a choice often referred to as making a page builder experience for content authors.
Real sections, of course, wouldn’t be silly prototypes like “pink” and “blue” and “task list.” They’d be ideas like “hero,” “feature,” “call to action,” “FAQs,” “preview cards,” or “testimonials.”
Technical
Many CMSes, including Sanity (the one I’ll be configuring in this post), allow programmers to extract data represented in the JSON punctuation standard, which is basically a computer-readable way of writing a bulleted list.
Among other uses, the extracted CMS data can be fed into a tool like a static site generator to build a web site.
Here’s an example of the JSON representation of my bulleted list doodle representing the “sections” of the web page I want to make.
Sanity configuration files
I’ll presume that this isn’t your first project with Sanity. If it is, please go do the project at Sanity Minimum Viable Build to learn your way around.
This post follows all the same steps that I took for the “minimum viable build,” except that my files in the schemas
folder are different.
I also put a few extra icons into a folder called static
because Sanity stopped offering them built as a built-in feature of its “Sanity Studio” editing interface.
A copy of my full Sanity configuration codebase is in this GitHub repository.
While building the configuration files, they lived in a folder on my computer called c:\example\sanity_movable\
.
/schemas/schema.js
schema.js
in the schemas
folder is the master file defining my Sanity schema.
I’ll use it to tell Sanity about all the other “schema definition” files I create (which in this case, there will be five of).
The contents of mine are:
// First, we must import the schema creator
import createSchema from 'part:@sanity/base/schema-creator'
// Then import schema types from any plugins that might expose them
import schemaTypes from 'all:part:@sanity/base/schema-type'
// We import object and document schemas
import landing from './documents/landing'
import sectionBlue from './objects/sectionBlue'
import sectionPink from './objects/sectionPink'
import sectionTaskList from './objects/sectionTaskList'
import task from './objects/task'
// Then we give our schema to the builder and provide the result to Sanity
export default createSchema({
// We name our schema
name: 'default',
// Then proceed to concatenate our document type
// to the ones provided by any plugins that are installed
types: schemaTypes.concat([
// The following are document types which will appear
// in the studio.
landing,
// When added to this list, object types can be used as
// { type: 'typename' } in other document schemas
sectionBlue,
sectionPink,
sectionTaskList,
task,
])
})
/schemas/documents/landing.js
I promised schema.js
a “landing
” schema in the documents
subfolder of the schemas
folder, so I added a new file at that location called landing.js
.
Its contents are as follows:
import icon from '../../static/icons/arrows-alt'
export default {
name: 'landing',
title: 'Landing Page',
type: 'document',
icon,
fields: [
{
name: 'template',
title: 'Template',
type: 'string',
required: true,
},
{
name: 'slug',
title: 'Slug',
type: 'slug',
required: true,
description: 'If not happy with what the system generated, you can hand-edit it here'
},
{
name: 'sections',
title: 'Sections',
type: 'array',
of: [{type: 'sectionBlue'}, {type: 'sectionPink'}, {type: 'sectionTaskList'}],
},
],
preview: {
select: {
slug: 'slug',
template: 'template',
},
prepare(selection) {
const {slug, template} = selection
return {
title: `${slug.current} [${template}]`,
}
}
}
}
I promised schema.js
and landing.js
3 more schemas in the objects
subfolder of the schemas
folder: “sectionBlue
,” “sectionPink
,” and “sectionTaskList
”. Consequently, I added 3 more .js
-typed files at those locations:
/schemas/documents/sectionBlue.js
import icon from '../../static/icons/bluetooth'
export default {
name: 'sectionBlue',
title: 'Section - Blue',
type: 'object',
icon,
fields: [
{
name: 'mention',
title: 'Mention',
description: 'What text would you like in this "blue" section?',
type: 'string'
},
],
preview: {
select: {
mention: 'mention',
},
prepare(selection) {
const {mention} = selection
return {
title: mention,
}
}
}
}
/schemas/documents/sectionPink.js
import icon from '../../static/icons/cake'
export default {
name: 'sectionPink',
title: 'Section - Pink',
type: 'object',
icon,
fields: [
{
name: 'say',
title: 'Say',
description: 'What text would you like in this "pink" section?',
type: 'string'
},
],
preview: {
select: {
say: 'say'
},
prepare(selection) {
const {say} = selection
return {
title: say,
}
}
}
}
/schemas/documents/sectionTaskList.js
import icon from '../../static/icons/tasks'
export default {
name: 'sectionTaskList',
title: 'Section - Task List',
type: 'object',
icon,
fields: [
{
name: 'accomplishments',
title: 'Tasks/accomplishments',
type: 'array',
of:[{type:'task'}],
},
],
}
I promised schema.js
and sectionTaskList.js
1 last schema in the objects
subfolder of the schemas
folder: “task
”. So I added 1 last file, task.js
:
/schemas/documents/task.js
import icon from '../../static/icons/check-box-outline-blank'
export default {
name: 'task',
title: 'Task For Me',
type: 'object',
icon,
fields: [
{
name: 'task',
title: 'Task',
type: 'string',
required: true,
},
{
name: 'done',
title: 'Done?',
type: 'boolean',
required: true,
},
{
name: 'how',
title: 'How?',
type: 'string',
required: false,
},
],
}
/static/icons/*.js
Finally, I promised all of my individual schema files some “icons” to make the content editing experience pretty: a bluetooth symbol for “blue sections,” a birthday cake for “pink sections,” etc.
I copied a few “.js” files out of older Sanity projects that had installed a NPM moduled called react-icons
on my behalf and pasted them into c:\example\sanity_movable\static\icons\
.
You can grab copies of them from my repo on Github.
Alternatively, for more icons, install the react-icons
NPM package on your computer somewhere. Then find and copy files you like better from within its folder structure. Here’s a nice “React icon” preview tool for deciding which ones you like.
Deploying Sanity Studio
I followed the same steps to “deploy” the configuration files I’d built into Sanity’s cloud as I followed in the “minimum viable build” project.
This created a “Sanity Studio” web site for editing content in my Sanity project.
Data loading
Instead of making our first Sanity “document” by hand, let’s cheat and data-load one in, formatted in the JSON style.
Note that instead of letting Sanity generate an ID for the document, I’m going forcing its _id
property to the phrase sample_landings_home
.
This will help me data-load an edit if I realize later that I made some typos. (You can make a lot of typos at once with large-scale data migrations. This was a lifesaver with the project I’m in the middle of, migrating Gigpress to Sanity.)
{
"_id": "sample_landings_home",
"_type": "landing",
"template": "landing",
"slug": {
"_type": "slug",
"current": "/"
},
"sections": [{
"_type": "sectionPink",
"say": "I did it!"
}, {
"_type": "sectionTaskList",
"accomplishments": [{
"_type": "task",
"done": true,
"how": "well",
"task": "eat"
}, {
"_type": "task",
"done": false,
"how": "soundly",
"task": "sleep"
}, {
"_type": "task",
"done": true,
"how": "high",
"task": "jump"
}, {
"_type": "task",
"done": true,
"task": "write"
}, {
"_type": "task",
"done": true,
"how": "regularly",
"task": "hydrate"
}
]
}, {
"_type": "sectionBlue",
"mention": "Hello World"
}
]
}
When actually loading the data, I have to use a tool (like the JSMin command of Sun Junwen’s JSTool plugin for Notepad++) to concatenate this one JSON record into a single line of text – no line breaks.
Therefore, what I’ve actually done is create a file at c:\example\sanity_movable\data_samples\sample_landings.ndjson
that looks like this:
{"_id":"sample_landings_home","_type":"landing","template":"landing","slug":"/","sections":[{"_type":"sectionPink","say":"I did it!"},{"_type":"sectionTaskList","accomplishments":[{"_type":"task","done":true,"how":"well","task":"eat"},{"_type":"task","done":false,"how":"soundly","task":"sleep"},{"_type":"task","done":true,"how":"high","task":"jump"},{"_type":"task","done":true,"task":"write"},{"_type":"task","done":true,"how":"regularly","task":"hydrate"}]},{"_type":"sectionBlue","mention":"Hello World"}]}
Trivia: the filename ends in .ndjson
, not .json
, to remind me that it’s not a pure JSON file.
It’s a variant on JSON called “newline-delimited JSON,” which is the data standard Sanity chose for import operations.
In the NDJSON punctuation standard, line breaks need to be avoided with one exception:
If the outermost level of the file is a list, you leave off its [
and ]
, and you separate members of the list using line breaks instead of commas.
You can grab a copy of this file from my Github repository if you’d like to follow along.
From a command line prompt within the folder c:\example\sanity_movable\
on my computer, I executed the following command:
sanity dataset import data_samples/sample_landings.ndjson YOUR_SANITY_DATASET_NAME_GOES_HERE --replace
Exploring Sanity Studio
Logging into “Sanity studio” as before, I can see my “home page” as an author would see it:
The sections are rearrangeable by clicking the six-dot icon at their far left, holding the cursor down, and dragging them up and down. They’re deleteable by clicking the trash can icon at their far right.
I can add a section, too:
If I click on the section with a birthday cake (a “pink” section), I can edit the value of say
from I did it!
to something else.
If I click on the section that says “accomplishments”, a panel pops up where I can rearrange, delete, or add tasks:
And, clicking on the task that says “jump”, I can edit its properties.
Planning for web site generation
Sanity does not make web sites
None of what I’ve done in Sanity actually makes “pink” sections appear on a web page with a pink background.
That will be the job of a tool like a static site generator.
All I’ve done with Sanity is set up a nice way to organize my thoughts and to specify that I’d like a “pink”-typed section with the phrase “I did it!” to be the first section on my home page.
For what it’s worth, I made Eleventy build me a web site from this tutorial’s Sanity project – check it out at https://movable-11ty-sanity.netlify.app/ and peek at my 11ty configuration on GitHub.
Test APIs
Your static site generator will have to extract data from Sanity, so it’s probably a good idea to test API access to your Sanity data as before and, if you’re planning to build a web site with Gatsby, to set up GraphQL API access to Sanity.
Webhooks
Sanity is happy to change your data and never tell the server running your static site generator to rebuild your site.
Be sure to configure your static site generation server to rebuild and redeploy your web site upon receiving a “webhook.”
Configure Sanity to talk to that “webhook” every time you publish new content in Sanity.
Live previews
You should also know that for any one “document” you store in Sanity, you (or your static site generator) can do more than just extract the “published” version of it.
If a content author is currently editing the document but hasn’t yet “published” their changes, you can extract details as seen in the latest “draft” or few.
This might not be particularly useful in all site generation tools, but with tools that inject some special Javascript into the pages of your web site (like Gatsby), there are plugins and configuration settings that can make non-production versions of your web site change while your authors edit content.
With tools like that, if an author opened two browsers at once – one for editing content in Sanity, and the other for watching the relevant page of the non-production web site – they could see the impact of changes they’re thinking about making to the data stored in Sanity, live, while they edit and before they click “Publish.”
With more “truly static” site generators like Jekyll, Hugo, or 11ty, “live previews” of draft Sanity content might be a little trickier to implement. Hopefully one day I can get something working and show it off here.
Further reading
- A visual gallery of “web page section” ideas from Tailwind UI, in case you’re confused when I use design buzzwords like “hero” or “call to action”
- How to use structured content for page building by Sanity.io
- Create a page builder in Sanity for Gatsby by Simeon Griggs
- A few more screenshots of a page builder in Sanity by Daniel Kapper
- And even more screenshots of a page builder in Sanity by Mike Wagz
Posts in this series
- Part 1 - Salesforce: métadonnées personnalisées v. objets personnalisés
- Part 2 - Salesforce Custom Metadata vs. Custom Objects
- Part 3 - Pourquoi - et comment - apprendre PL/SQL (…T-SQL…PL/pgSQL…)
- Part 4 - Why & How Should I Learn PL/SQL? (…T-SQL…PL/pgSQL…)
- Part 5 - Every SQL Join You'll Ever Need
- Part 6 - Python for Salesforce Real-Life Challenge: NPSP Email Deduping
- Part 7 - Dedupe Salesforce NPSP Emails with Python
- Part 8 - Filtrer un gros fichier CSV avec Python
- Part 9 - Filter a large CSV file with Python
- Part 10 - Proper-Casing CSV/XLSX Data With Python
- Part 11 - Feb. 9: FRENCH-language demo of Python for Saleforce admins!
- Part 12 - 9 fév: Démonstration de Python pour admins Salesforce (FR)
- Part 13 - Python pour Salesforce: Énumérer les valeurs uniques à travers plusieurs champs Salesforce
- Part 14 - Python for Salesforce: List unique values found across similar Salesforce fields
- Part 15 - Modification des données CSV / XLSX en Python pour admins Salesforce : vidéo et notes
- Part 16 - Telling Sourcetree about one of my GitHub repositories
- Part 17 - Git brain dump
- Part 18 - Cleaning bad Pardot data with Python
- Part 19 - Studying programming by writing glossaries
- Part 20 - Git and SourceTree setup with AWS federation on Windows
- Part 21 - Logging into Salesforce's Marketing Cloud API (w/ Python or Postman)
- Part 22 - Setting up Python on Windows with Miniconda by Anaconda
- Part 23 - Setting up Python on Windows with Anaconda
- Part 24 - Python pour Salesforce: compte -> campagne
- Part 25 - Salesforce REST APIs: A High-Level Primer
- Part 26 - Logging into Salesforce's Pardot API (w/ Python)
- Part 27 - Setting up VSCode to edit Salesforce metadata
- Part 28 - XML for Salesforce Administrators: DemandTools Configuration
- Part 29 - Tutorial: Flow Apex-Defined Data Types for Salesforce Admins
- Part 30 - Intro to HTTP
- Part 31 - Tutoriel : types de données définis par Apex pour admins Salesforce
- Part 32 - Salesforce Apex Performance: new ID map vs. for loop
- Part 33 - Forcelandia 2019 XML & JSON Conference Talk Resources
- Part 34 - Python Pandas For Excel on vBrownBag - show notes
- Part 35 - No-Permissions Salesforce Profile w/ Python and Selenium
- Part 36 - Recalcul des champs de formule Salesforce en Apex
- Part 37 - Recalculating Salesforce Formula Fields in Apex
- Part 38 - Disabling Lightning Experience w/ Python and Selenium in Salesforce
- Part 39 - Better Salesforce Insert/Update Operations with Jitterbit Caching
- Part 40 - S'occuper d'une erreur FillDataElements en Jitterbit
- Part 41 - Clearing A Jitterbit FillDataElements Error
- Part 42 - Solved: 2 Salesforce ETL Errors
- Part 43 - Workaround: Salesforce Flow Picklist-Related Invalid Type Error
- Part 44 - Migrating my Salesforce org backups from Eclipse to VSCode without messing up Git
- Part 45 - Flow Invocable Apex with Any Salesforce Object Type
- Part 46 - Salesforce Spring '20 Community Guest User Apocalypse
- Part 47 - Tutorial: Flow External Services for Salesforce Admins
- Part 48 - Salesforce Classic slow in Chrome? Use Firefox until Spring '20
- Part 49 - Python NumPy code to compute when a mortgage will be free of PMI (Private Mortgage Insurance)
- Part 50 - Excel VBA code to compute when a mortgage will be free of PMI (Private Mortgage Insurance)
- Part 51 - Door Prize: Excel VBA code for turning your ex-PMI into principal payments
- Part 52 - Netlify CMS Jekyll Minimum Viable Build
- Part 53 - What is #FlattenTheCurve? Why is everything cancelled?
- Part 54 - Recompile dependent PL/SQL code after changes
- Part 55 - Le 'ou' booléen aux formules MassImpact de DemandTools
- Part 56 - Boolean 'OR' in DemandTools MassImpact formulas
- Part 57 - Virtual Conference Live Music
- Part 58 - Jamstack Conf takeaways and Hopin thoughts
- Part 59 - Audient iD14 sound check for iOS and Windows
- Part 60 - How I brought my Google Page Speed from 80 to 91 in 5 minutes
- Part 61 - PL/SQL Nested Queries
- Part 62 - Educators and students - get Figma graphic design software free
- Part 63 - Rate-limiting API requests in Python with a decorator
- Part 64 - Set plugin names and default visibility in Canvas course navigation menus
- Part 65 - What is static web hosting?
- Part 66 - What is a static site generator?
- Part 67 - Qu'est-ce que l'hébergement web statique ?
- Part 68 - Qu'est-ce qu'un générateur de site statique ?
- Part 69 - Why do bloggers love Markdown? Why use MDX? And other notes from MDXConf 2020
- Part 70 - Choosing a headless CMS without losing your head
- Part 71 - Reading a datetime with miliseconds into an Oracle DATE
- Part 72 - Sep. 9: Content creation panelist on Whiskey Wednesday podcast
- Part 73 - Sanity datatable serializer for 11ty sites
- Part 74 - Study plan for GraphQL in Salesforce Marketing Cloud
- Part 75 - Configuring public-private key pairs so 2 Linux machines can talk
- Part 76 - Infrastructure as code homework #1
- Part 77 - Setting up Windows Store Python with Pandas in VSCode
- Part 78 - ORA-29283 salut le monde
- Part 79 - ORA-29283 hello world
- Part 80 - Anonymous PL/SQL to write a CSV from SQL
- Part 81 - Re-indenting with Notepad++ regex replace
- Part 82 - Ellucian Banner fan wiki, OMG
- Part 83 - Algolia attributes are flexible
- Part 84 - Infrastructure as code homework #2
- Part 85 - Drupal vs. 11ty with kittens & puppies
- Part 86 - Wanted: mirrorless camera & lens for livestreaming
- Part 87 - Hello World on Netlify Functions, Cloudflare Workers, & Vercel Functions
- Part 88 - How to break (and rebuild) the Jamstack
- Part 89 - How to use Jekyll SSG with Headless CMS
- Part 90 - Clean up old node_modules in Windows 10
- Part 91 - The first JavaScript I ever wrote
- Part 92 - APIs are for admins: texting your colleagues with Salesforce Flow (Cactusforce 2021)
- Part 93 - 5 things you need to know about Jekyll vs. 11ty Liquid includes
- Part 94 - Insert, update, and delete from a database to Salesforce
- Part 95 - My video editing software
- Part 96 - Data Development and Integrations (official Salesforce Developers podcast appearance)
- Part 97 - Stackbit can teach you web development
- Part 98 - Can 11ty host my podcast for free?
- Part 99 - A Lyris to Marketing Cloud imaginary architecture
- Part 100 - Off-by-one errors in Python API pagination
- Part 101 - Tailwind JIT starter for 11ty (play fast!)
- Part 102 - Anonymous PLSQL subquery from a file
- Part 103 - Checking a password against Troy Hunt's list
- Part 104 - Big Data: What to learn
- Part 105 - Tailwind JIT starter for Gatsby
- Part 106 - A git feature commit squashing approach
- Part 107 - Hello world, it's the AWS parameter store
- Part 108 - SEO savings, one bubble tea at a time
- Part 109 - Growing link underline, in Tailwind CSS
- Part 110 - Jamstack live preview tooling
- Part 111 - Adding Tailwind to a Jekyll site on Windows
- Part 112 - Open a Salesforce scratch org in a different browser with CumulusCI
- Part 113 - Random handy CumulusCI notes
- Part 114 - Database fiscal year in review: 2020/2021
- Part 115 - ETL QA is interpersonal communication
- Part 116 - A dumb Python mistake
- Part 117 - Learn static site generation with Stackbit
- Part 118 - Naomi Kritzer will be blogging some Minnesota 2021 candidates again
- Part 119 - Jekyll doesn't do components? Liar!
- Part 120 - But WHY do you need a database?
- Part 121 - Customizing Sanity Studio beyond the schema
- Part 122 - Document Salesforce TDTM to avoid dev-admin handoff issues
- Part 123 - Inspecting an existing 11ty project
- Part 124 - How I added Algolia search: front-end
- Part 125 - WCOnline API explainer
- Part 126 - Localhost HTML with Python on Windows
- Part 127 - How I set up VSCode for a new Salesforce project
- Part 128 - Salesforce Apex lets you use external IDs
- Part 129 - Read an NDJSON file with anonymous PL/SQL
- Part 130 - Connect to a database from Windows
- Part 131 - SFTP and SSH authentication patterns
- Part 132 - Cover Letter Flowchart
- Part 133 - Cover Letter Examples
- Part 134 - Loop through Trigger.new all you want
- Part 135 - Check a URL with Selenium IDE
- Part 136 - Find a library job with Python
- Part 137 - SSH or SFTP into a Linux server from a Windows machine with key-based authentication
- Part 138 - Pulumi & AWS minimum viable build
- Part 139 - Enterprise application diary
- Part 140 - Skyvia test - Salesforce to database
- Part 141 - Welcome to Minneapolis, Salesforce!
- Part 142 - Cover letter workshop - WITness Success / Minnebar
- Part 143 - UNPIVOT to key-value pair in Oracle SQL
- Part 144 - Transforming CommonApp into Salesforce EASY question responses
- Part 145 - Find and replace Pardot PML with HML
- Part 146 - Jitterbit: backfill Salesforce ID into a database
- Part 147 - Multiple Pardot accounts sending from the same domain
- Part 148 - Compare Flow versions in VSCode for Salesforce
- Part 149 - Download your org schema with CumulusCI
- Part 150 - CumulusCI Minimum Viable Build
- Part 151 - Jitterbit: replace a non-breaking space
- Part 152 - Git and GitHub exercises for beginners
- Part 153 - SFDX and Git exercises for beginners
- Part 154 - Use Subflow EVERYWHERE
- Part 155 - Securing CI/CD pipelines
- Part 156 - Should CI testing always be automated?
- Part 157 - DevOps vs. ITSM Maturity
- Part 158 - Our sales rep territories
- Part 159 - What is Salesforce Flow?
- Part 160 - I'm a DevOp!
- Part 161 - Azure Static Web Apps minimum viable build
- Part 162 - Running UI tests in GitHub Actions
- Part 163 - CI/CD get-to-know-you questions
- Part 164 - Azure DevOps vs. GitHub repositories and pipelines
- Part 165 - Quick -- I need a blank scratch org
- Part 166 - Reuse your Flows with Subflow
- Part 167 - Git best practices with Azure Data Factory
- Part 168 - Variables and secrets for CI/CD pipelines
- Part 169 - How many Entra App Registrations do I need?
- Part 170 - What is Azure DevOps?
- Part 171 - Trunk-based Git branching
- Part 172 - Code Review: My first Powershell function
- Part 173 - Entra/Azure System-Assigned Managed Identity FAQ
- Part 174 - Entra App Registration FAQ
- Part 175 - Azure RBAC Role Assignment FAQ
- Part 176 - Entra RBAC Role Assignment FAQ
- Part 177 - Low-Code Generation Z
- Part 178 - Where are Sitecore's paper plates?
- Part 179 - Add the Sitecore CLI to the Windows Software Center
- Part 180 - Configuring multiple Git accounts on my computer
- Part 181 - Use Git in the browser if you can
- Part 182 - Buy then build