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

  1. GitHub Actions workflow completed successfully.
  2. SWA portal showed zero Functions under the API.
  3. Every /api/* endpoint returned 404.
  4. 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:

  1. Build the package locally (npm run build -w @acme/shared-core).
  2. Copy the compiled output into api/bundled-deps/@acme/shared-core.
  3. Install its npm dependencies in place so the bundle is self-contained.
  4. Rewrite package.json to point at the bundled folder (file:./bundled-deps/@acme/shared-core).
  5. Proceed with the usual Azure/static-web-apps-deploy@v1 step; 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-scripts runs inside the bundled folder so its own package.json pulls down dependencies. This prevents runtime MODULE_NOT_FOUND errors for any transitive modules that aren’t published publicly.
  • The root package.json continues to use file:../packages/shared-core for local development. Only the deployment artifact is rewritten.
  • Local func start still 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-deps folder, then rewrite package.json before 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