
· 3 min read
Deploying SPFx apps with heft and DevOps pipelines
In this post, we will explore how to deploy SharePoint Framework (SPFx) applications using Heft and DevOps pipelines. Heft is a build system that provides a streamlined workflow for building and deploying SPFx solutions introduced with SPFx 1.21.
Martin wrote an awesome step by step blog post on how to set up a DevOps pipeline for SPFx solutions: Deploying SPFx apps with Azure DevOps pipelines (all YAML), yet with SPFx 1.21 you can now use Heft to simplify the build and deployment process even further. All steps to upgrade your local development environment and your DevOps pipelines are described in the official documentation: SharePoint Framework v1.21 release notes.
Devops Pipeline
The general setup will be exactly the same as before, but gulp is gone and heft is in, and heft does require a slightly different call. So assuming you have updated your local development environment and your SPFx solution to 1.21, you can update your pipeline to use Heft instead of gulp. If you want to learn more about heft read up on Understanding the Heft-based toolchain (how it works).
You can keep the exact same settings as Martin has described and introduce npx heft test --clean --production instead of gulp build --ship and npx heft package-solution --production instead of gulp bundle --ship and gulp package-solution --ship, resulting in the following full YAML pipeline:
trigger:
branches:
include:
- main
pool:
vmImage: "ubuntu-latest"
stages:
- stage: Build
variables:
- group: "Deployment Production"
jobs:
- job: Build
steps:
- task: NodeTool@0
displayName: "Use Node 22.x"
inputs:
versionSpec: "22.x"
- task: Npm@1
displayName: "npm install"
inputs:
command: "install"
- script: npx heft test --clean --production
displayName: "Heft build & test"
- script: npx heft package-solution --production
displayName: "Heft package-solution"
- task: CopyFiles@2
displayName: "Copy sppkg to staging"
inputs:
Contents: "sharepoint/**/*.sppkg"
TargetFolder: "$(Build.ArtifactStagingDirectory)"
flattenFolders: true
- task: PublishBuildArtifacts@1
displayName: "Publish artifact"
inputs:
artifactName: "drop"
- stage: ReleaseToProduction
displayName: "Release to Production"
condition: succeeded('Build')
variables:
- group: "Deployment Production"
jobs:
- deployment: ReleaseToProduction
displayName: "Release to Production"
environment: "Production"
strategy:
runOnce:
deploy:
steps:
- task: DownloadSecureFile@1
name: caCertificate
displayName: "Download certificate"
inputs:
secureFile: "CI-CD-Certificate.pfx"
- task: NodeTool@0
displayName: "Use Node 22.x"
inputs:
versionSpec: "22.x"
- task: Npm@1
displayName: "Install CLI for Microsoft 365"
inputs:
command: custom
verbose: false
customCommand: "install -g @pnp/cli-microsoft365"
- script: |
m365 login --authType certificate --certificateFile "$(caCertificate.secureFilePath)" --password "$(CertificatePassword)" --appId "$(EntraIDAppId)" --tenant "$(TenantId)"
m365 spo set --url "$(SharePointBaseUrl)"
m365 spo app add --filePath "$(Pipeline.Workspace)/drop/mypackage.sppkg" --overwrite
m365 spo app deploy --name "mypackage.sppkg"
displayName: "Add and deploy app"
All in all a pretty small change, but it does simplify the build and deployment process and makes it more consistent with the local development experience.

Albert-Jan Schot
CTO, Microsoft MVP & FastTrack Recognized Solution Architect
I am Albert-Jan Schot, CTO at Blis Digital, Microsoft MVP, and FastTrack Recognized Solution Architect focused on Microsoft 365, Azure, and AI agents. I help teams turn complex Microsoft Cloud challenges into practical architecture decisions and shipped outcomes.
Zuid Holland, Netherlands


