I recently spent an embarrassing amount of time chasing a “green” Azure Static Web Apps (SWA) deployment that still left me with a dead API—and even GitHub Copilot kept looping on red herrings, unable to spot what was really wrong. The culprit was a private workspace package - @acme/shared-core - that the Functions runtime could not see once Oryx built the managed API. This post walks through what happened, why the failure was completely silent from SWA’s point of view, and how I now bundle local packages (including their transitive npm dependencies) before handing anything to Oryx.
Stack at a Glance
- Monorepo managed via npm workspaces.
/api: Azure Functions app targeting Node 18./packages/shared-core: shared domain logic referenced as"@acme/shared-core": "file:../packages/shared-core".- Deployment: Azure Static Web Apps with the integrated managed Functions runtime (Oryx builds on the server side).
Symptoms: A Perfectly Quiet Failure
- GitHub Actions workflow completed successfully.
- SWA portal showed zero Functions under the API.
- Every
/api/*endpoint returned 404. - Application Insights contained no traces, exceptions, or console logs.
From SWA’s perspective, it looked like I had deployed an empty API. In reality, the Functions host crashed before registering any triggers, so SWA never associated routes, and App Insights never bootstrapped.
Root Cause
The SWA workflow uploads only the contents of api-new. During the run, Oryx installs dependencies, compiles TypeScript, and produces a bundle. Because @acme/shared-core is referenced via a relative path, the folder simply isn’t inside the uploaded artifact:
api/
package.json (points to ../packages/shared-core)
...
When the Functions worker starts, it tries to require('@acme/shared-core'). Node resolves the path into thin air, throws, and the host dies before emitting logs. Since no function metadata ever loads, the SWA runtime treats the API as empty—hence the clean deploy paired with 404s and a silent App Insights.
A secondary problem: even if I copy packages/shared-core into api, that package has its own npm dependencies (zod, uuid, …). Without node_modules alongside the bundled copy, the Function still dies while importing those transitives.
Fix: Vendor Everything Before Oryx Touches It
The remediation is to make @acme/shared-core look like a normal in-tree dependency before the deploy step runs:
- Build the package locally (
npm run build -w @acme/shared-core). - Copy the compiled output into
api/bundled-deps/@acme/shared-core. - Install its npm dependencies in place so the bundle is self-contained.
- Rewrite package.json to point at the bundled folder (
file:./bundled-deps/@acme/shared-core). - Proceed with the usual
Azure/static-web-apps-deploy@v1step; Oryx now sees a plain directory.
Workflow snippet:
- name: Prepare API dependencies (bundle @acme/shared-core)
run: |
set -euo pipefail
npm run build -w @acme/shared-core
mkdir -p api/bundled-deps/@acme
rm -rf api/bundled-deps/@acme/shared-core
cp -R packages/shared-core api/bundled-deps/@acme/shared-core
pushd api/bundled-deps/@acme/shared-core >/dev/null
npm install --production --ignore-scripts
popd >/dev/null
cd api-new
npm pkg set dependencies.@acme/shared-core="file:./bundled-deps/@acme/shared-core"
Key details:
npm install --production --ignore-scriptsruns inside the bundled folder so its ownpackage.jsonpulls down dependencies. This prevents runtimeMODULE_NOT_FOUNDerrors for any transitive modules that aren’t published publicly.- The root
package.jsoncontinues to usefile:../packages/shared-corefor local development. Only the deployment artifact is rewritten. - Local
func startstill uses workspace symlinks; CI/CD alone performs the bundling.
Verifying the Fix
- After bundling, SWA immediately listed the HTTP triggers under the API section.
- App Insights lit up with cold-start traces and log statements again.
- Endpoints returned real responses, and CLI curl checks started passing.
- Subsequent deploys remain green because Oryx never needs to discover files outside the API directory.
Lessons Learned
- A successful SWA deployment does not guarantee the Functions host loaded—check if the portal lists functions.
- App Insights can be blank if the host fails before telemetry initialization.
- Any dependency referenced via must be physically present inside the API artifact; copying source files isn’t enough if that package has its own dependencies.
- Bundle local packages (and their transitives) into an
api/bundled-depsfolder, then rewritepackage.jsonbefore invoking the SWA deploy action. - If you’re running SWA managed Functions from a monorepo with unpublished packages, treat those packages like vendored libraries: build, copy, npm install, and only then let Oryx do its thing. It keeps deployments boring and avoids silent 404s that look like an empty API.
Filed under notes-to-self
