Repository: pwrdrvr/microapps-core (language: python)

Issue:
v0.3 - Make API Gateway optional
## Motivations
- CDK support for HTTP API Gateways is currently alpha
- The new Lambda Function URL support is currently all that is used
- Removing deploy of API Gateway now would speed up deploys

## To-Do
- [x] Make API Gateway optional in all CDK constructs
- [x] Allow the API Gateway Origin to be missing on the CloudFront distribution
- [x] Use S3 as the default Origin on CloudFront
- [x] Add support to change the S3 Origin to a Custom Origin (Lambda etc)
~~- [ ] Move `MicroAppsAPIGwy` into it's own CDK Construct~~
~~- [ ] Remove API Gateway as an option from the `MicroApps` CDK Construct~~
~~- [ ] Remove the API Gateway npm modules for CDK from the microapps-cdk package~~
~~- [ ] Do not publish the API Gateway CDK Construct for now~~
~~- [ ] Publish the updated MicroApps construct~~

Items moved to #293 for moving the support into a new module in next minor version.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e4de724b..42cf6182 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -47,10 +47,34 @@ jobs:
         uses: actions/setup-node@v3
         with:
           node-version: 16
-          cache: 'yarn'
-          cache-dependency-path: '**/yarn.lock'
+          # Note: this cache is not particularly useful...
+          # It is about 780 MB and is only needed if we run `yarn install`,
+          # which we only do if `yarn.lock` has changed, which means this cacue
+          # key is changed in that case... so... it's not useful in the one
+          # case where we need it because we are caching installed modules below.
+          # cache: 'yarn'
+          # cache-dependency-path: '**/yarn.lock'
+
+      # https://www.jonathan-wilkinson.com/github-actions-cache-everything
+      - name: Cache Node Modules
+        id: cache-node-modules
+        uses: actions/cache@v3
+        with:
+          path: |
+            **/node_modules
+          key: node-modules-ci-${{ hashFiles('**/yarn.lock') }}
+
+      - name: Optionally Install Node Modules
+        if: steps.cache-node-modules.outputs.cache-hit != 'true'
+        run: yarn install --frozen-lockfile
 
-      - name: Install Node Modules
+      # We do this here so the `build-jsii` modules are installed too
+      # and become part of the cache - if we don't then
+      # their install will get skipped during `build-jsii` but they won't
+      # be present in the cache so the build will fail
+      - name: Optionally Install CDK Construct Deps
+        if: steps.cache-node-modules.outputs.cache-hit != 'true'
+        working-directory: packages/microapps-cdk/
         run: yarn install --frozen-lockfile
 
       - name: Extract App NPM Versions
@@ -127,10 +151,25 @@ jobs:
         uses: actions/setup-node@v3
         with:
           node-version: 16
-          cache: 'yarn'
-          cache-dependency-path: '**/yarn.lock'
+          # Note: this cache is not particularly useful...
+          # It is about 780 MB and is only needed if we run `yarn install`,
+          # which we only do if `yarn.lock` has changed, which means this cacue
+          # key is changed in that case... so... it's not useful in the one
+          # case where we need it because we are caching installed modules below.
+          # cache: 'yarn'
+          # cache-dependency-path: '**/yarn.lock'
+
+      # https://www.jonathan-wilkinson.com/github-actions-cache-everything
+      - name: Cache Node Modules
+        id: cache-node-modules
+        uses: actions/cache@v3
+        with:
+          path: |
+            **/node_modules
+          key: node-modules-ci-${{ hashFiles('**/yarn.lock') }}
 
-      - name: Install Node Modules
+      - name: Optionally Install Node Modules
+        if: steps.cache-node-modules.outputs.cache-hit != 'true'
         run: yarn install --frozen-lockfile
 
       - name: Build All TypeScript
@@ -164,18 +203,22 @@ jobs:
             npx replace-in-file "/\/u002F${RELEASE_APP_NAME}/g" ${PREFIX_U}/u002F${RELEASE_APP_NAME} --configFile=.release-replace.config.js --isRegex
           fi
 
+      #
+      # Synth and upload if BUILD-CDK-ZIP label is present
+      #
       - name: Synth CDK Stack
+        if: github.event_name == 'pull_request' && contains( github.event.pull_request.labels.*.name, 'BUILD-CDK-ZIP')
         run: |
           npx cdk synth --context @pwrdrvr/microapps:deployDemoApp=true \
             --context @pwrdrvr/microapps:deployNexjsDemoApp=true \
             --context @pwrdrvr/microapps:deployReleaseApp=true \
             --require-approval never ${{ matrix.deployName }}
-
-      # Upload Synth
       - name: Zip Package
+        if: github.event_name == 'pull_request' && contains( github.event.pull_request.labels.*.name, 'BUILD-CDK-ZIP')
         working-directory: .
         run: zip -r cdk-out.zip cdk.out
       - name: Upload Zip
+        if: github.event_name == 'pull_request' && contains( github.event.pull_request.labels.*.name, 'BUILD-CDK-ZIP')
         uses: actions/upload-artifact@v3
         with:
           name: cdk_out_${{ matrix.deployName }}
@@ -226,7 +269,7 @@ jobs:
           description: 'Passed'
           state: 'success'
           sha: ${{github.event.pull_request.head.sha || github.sha}}
-          target_url: https://${{ steps.getCDKExports.outputs.edgeDomain }}${{ steps.getCDKExports.outputs.prefix }}/${{ env.DEMO_APP_NAME }}?appver=${{ env.PACKAGE_VERSION }}
+          target_url: https://${{ steps.getCDKExports.outputs.edgeDomain }}${{ steps.getCDKExports.outputs.prefix }}/${{ env.DEMO_APP_NAME }}/?appver=${{ env.PACKAGE_VERSION }}
 
       - name: Test Demo App
         run: |
@@ -310,7 +353,7 @@ jobs:
           description: 'Passed'
           state: 'success'
           sha: ${{github.event.pull_request.head.sha || github.sha}}
-          target_url: https://${{ steps.getCDKExports.outputs.edgeDomain }}${{ steps.getCDKExports.outputs.prefix }}/${{ env.NEXTJS_DEMO_APP_NAME }}/${{ needs.build.outputs.nextjsDemoAppPackageVersion }}
+          target_url: https://${{ steps.getCDKExports.outputs.edgeDomain }}${{ steps.getCDKExports.outputs.prefix }}/${{ env.NEXTJS_DEMO_APP_NAME }}?appver=${{ needs.build.outputs.nextjsDemoAppPackageVersion }}
 
       - name: Test Nextjs Demo App
         run: |
@@ -341,7 +384,7 @@ jobs:
           description: 'Passed'
           state: 'success'
           sha: ${{github.event.pull_request.head.sha || github.sha}}
-          target_url: https://${{ steps.getCDKExports.outputs.edgeDomain }}${{ steps.getCDKExports.outputs.prefix }}/${{ env.RELEASE_APP_NAME }}/${{ needs.build.outputs.releaseAppPackageVersion }}
+          target_url: https://${{ steps.getCDKExports.outputs.edgeDomain }}${{ steps.getCDKExports.outputs.prefix }}/${{ env.RELEASE_APP_NAME }}?appver=${{ needs.build.outputs.releaseAppPackageVersion }}
 
       - name: Test Release App
         run: |
@@ -362,10 +405,25 @@ jobs:
         uses: actions/setup-node@v3
         with:
           node-version: 16
-          cache: 'yarn'
-          cache-dependency-path: '**/yarn.lock'
+          # Note: this cache is not particularly useful...
+          # It is about 780 MB and is only needed if we run `yarn install`,
+          # which we only do if `yarn.lock` has changed, which means this cacue
+          # key is changed in that case... so... it's not useful in the one
+          # case where we need it because we are caching installed modules below.
+          # cache: 'yarn'
+          # cache-dependency-path: '**/yarn.lock'
+
+      # https://www.jonathan-wilkinson.com/github-actions-cache-everything
+      - name: Cache Node Modules
+        id: cache-node-modules
+        uses: actions/cache@v3
+        with:
+          path: |
+            **/node_modules
+          key: node-modules-ci-${{ hashFiles('**/yarn.lock') }}
 
-      - name: Install Node Modules
+      - name: Optionally Install Node Modules
+        if: steps.cache-node-modules.outputs.cache-hit != 'true'
         run: yarn install --frozen-lockfile
 
       # - name: Generate Projen Files
@@ -385,7 +443,8 @@ jobs:
       # - name: Move root modules out of the way for CDK Construct build
       #   run: mv node_modules node_modules_hide
       
-      - name: Install CDK Construct Deps
+      - name: Optionally Install CDK Construct Deps
+        if: steps.cache-node-modules.outputs.cache-hit != 'true'
         working-directory: packages/microapps-cdk/
         run: yarn install --frozen-lockfile
 
diff --git a/packages/demo-app/src/index.ts b/packages/demo-app/src/index.ts
index e78e84d4..4c6a2900 100644
--- a/packages/demo-app/src/index.ts
+++ b/packages/demo-app/src/index.ts
@@ -11,6 +11,9 @@ export async function handler(
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   context?: lambda.Context,
 ): Promise<unknown> {
+  // eslint-disable-next-line no-console
+  console.log('event', event);
+
   if (event.rawPath.endsWith('/serverIncrement')) {
     const currValue = parseInt(event.queryStringParameters?.currValue ?? '0', 10);
     const newValue = currValue + 1;
diff --git a/packages/microapps-cdk/API.md b/packages/microapps-cdk/API.md
index 99f26563..ddd0aa3e 100644
--- a/packages/microapps-cdk/API.md
+++ b/packages/microapps-cdk/API.md
@@ -48,14 +48,6 @@ new MicroApps(scope: Construct, id: string, props?: MicroAppsProps)
 
 #### Properties <a name="Properties"></a>
 
-##### `apigwy`<sup>Required</sup> <a name="@pwrdrvr/microapps-cdk.MicroApps.apigwy"></a>
-
-- *Type:* [`@pwrdrvr/microapps-cdk.IMicroAppsAPIGwy`](#@pwrdrvr/microapps-cdk.IMicroAppsAPIGwy)
-
-{@inheritdoc IMicroAppsAPIGwy}.
-
----
-
 ##### `cf`<sup>Required</sup> <a name="@pwrdrvr/microapps-cdk.MicroApps.cf"></a>
 
 - *Type:* [`@pwrdrvr/microapps-cdk.IMicroAppsCF`](#@pwrdrvr/microapps-cdk.IMicroAppsCF)
@@ -80,6 +72,14 @@ new MicroApps(scope: Construct, id: string, props?: MicroAppsProps)
 
 ---
 
+##### `apigwy`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroApps.apigwy"></a>
+
+- *Type:* [`@pwrdrvr/microapps-cdk.IMicroAppsAPIGwy`](#@pwrdrvr/microapps-cdk.IMicroAppsAPIGwy)
+
+{@inheritdoc IMicroAppsAPIGwy}.
+
+---
+
 ##### `edgeToOrigin`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroApps.edgeToOrigin"></a>
 
 - *Type:* [`@pwrdrvr/microapps-cdk.IMicroAppsEdgeToOrigin`](#@pwrdrvr/microapps-cdk.IMicroAppsEdgeToOrigin)
@@ -498,15 +498,16 @@ import { AddRoutesOptions } from '@pwrdrvr/microapps-cdk'
 const addRoutesOptions: AddRoutesOptions = { ... }
 ```
 
-##### `apiGwyOrigin`<sup>Required</sup> <a name="@pwrdrvr/microapps-cdk.AddRoutesOptions.apiGwyOrigin"></a>
+##### `appOrigin`<sup>Required</sup> <a name="@pwrdrvr/microapps-cdk.AddRoutesOptions.appOrigin"></a>
 
 - *Type:* [`aws-cdk-lib.aws_cloudfront.IOrigin`](#aws-cdk-lib.aws_cloudfront.IOrigin)
+- *Default:* invalid URL (never used)
 
-API Gateway CloudFront Origin for API calls.
+Default origin (invalid URL or API Gateway).
 
 ---
 
-##### `apigwyOriginRequestPolicy`<sup>Required</sup> <a name="@pwrdrvr/microapps-cdk.AddRoutesOptions.apigwyOriginRequestPolicy"></a>
+##### `appOriginRequestPolicy`<sup>Required</sup> <a name="@pwrdrvr/microapps-cdk.AddRoutesOptions.appOriginRequestPolicy"></a>
 
 - *Type:* [`aws-cdk-lib.aws_cloudfront.IOriginRequestPolicy`](#aws-cdk-lib.aws_cloudfront.IOriginRequestPolicy)
 
@@ -533,7 +534,7 @@ CloudFront Distribution to add the Behaviors (Routes) to.
 ##### `createAPIPathRoute`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.AddRoutesOptions.createAPIPathRoute"></a>
 
 - *Type:* `boolean`
-- *Default:* true
+- *Default:* false
 
 Create an extra Behavior (Route) for /api/ that allows API routes to have a period in them.
 
@@ -547,7 +548,7 @@ even if they have a period in the path.
 ##### `createNextDataPathRoute`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.AddRoutesOptions.createNextDataPathRoute"></a>
 
 - *Type:* `boolean`
-- *Default:* true
+- *Default:* false
 
 Create an extra Behavior (Route) for /_next/data/ This route is used by Next.js to load data from the API Gateway on `getServerSideProps` calls.  The requests can end in `.json`, which would cause them to be routed to S3 if this route is not created.
 
@@ -772,14 +773,6 @@ S3 bucket origin for deployed applications.
 
 ---
 
-##### `httpApi`<sup>Required</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsCFProps.httpApi"></a>
-
-- *Type:* [`@aws-cdk/aws-apigatewayv2-alpha.HttpApi`](#@aws-cdk/aws-apigatewayv2-alpha.HttpApi)
-
-API Gateway v2 HTTP API for apps.
-
----
-
 ##### `assetNameRoot`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsCFProps.assetNameRoot"></a>
 
 - *Type:* `string`
@@ -817,7 +810,7 @@ ACM Certificate that covers `domainNameEdge` name.
 ##### `createAPIPathRoute`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsCFProps.createAPIPathRoute"></a>
 
 - *Type:* `boolean`
-- *Default:* true
+- *Default:* true if httpApi is provided
 
 Create an extra Behavior (Route) for /api/ that allows API routes to have a period in them.
 
@@ -831,7 +824,7 @@ even if they have a period in the path.
 ##### `createNextDataPathRoute`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsCFProps.createNextDataPathRoute"></a>
 
 - *Type:* `boolean`
-- *Default:* true
+- *Default:* true if httpApi is provided
 
 Create an extra Behavior (Route) for /_next/data/ This route is used by Next.js to load data from the API Gateway on `getServerSideProps` calls.  The requests can end in `.json`, which would cause them to be routed to S3 if this route is not created.
 
@@ -869,6 +862,26 @@ Configuration of the edge to origin lambda functions.
 
 ---
 
+##### `httpApi`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsCFProps.httpApi"></a>
+
+- *Type:* [`@aws-cdk/aws-apigatewayv2-alpha.HttpApi`](#@aws-cdk/aws-apigatewayv2-alpha.HttpApi)
+
+API Gateway v2 HTTP API for apps.
+
+---
+
+##### `originShieldRegion`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsCFProps.originShieldRegion"></a>
+
+- *Type:* `string`
+- *Default:* none
+
+Optional Origin Shield Region.
+
+This should be the region where the DynamoDB is located so the
+EdgeToOrigin calls have the lowest latency (~1 ms).
+
+---
+
 ##### `r53Zone`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsCFProps.r53Zone"></a>
 
 - *Type:* [`aws-cdk-lib.aws_route53.IHostedZone`](#aws-cdk-lib.aws_route53.IHostedZone)
@@ -983,6 +996,15 @@ Path prefix on the root of the API Gateway Stage.
 
 ---
 
+##### `setupApiGatewayPermissions`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsEdgeToOriginProps.setupApiGatewayPermissions"></a>
+
+- *Type:* `boolean`
+- *Default:* false
+
+Enable invoking API Gateway from the Edge Lambda.
+
+---
+
 ##### `signingMode`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsEdgeToOriginProps.signingMode"></a>
 
 - *Type:* `string`
@@ -1080,6 +1102,15 @@ Certificate in deployed region for the API Gateway.
 
 ---
 
+##### `createAPIGateway`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsProps.createAPIGateway"></a>
+
+- *Type:* `boolean`
+- *Default:* false
+
+Create API Gateway for non-edge invocation.
+
+---
+
 ##### `createAPIPathRoute`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsProps.createAPIPathRoute"></a>
 
 - *Type:* `boolean`
@@ -1094,6 +1125,20 @@ even if they have a period in the path.
 
 ---
 
+##### `createNextDataPathRoute`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsProps.createNextDataPathRoute"></a>
+
+- *Type:* `boolean`
+- *Default:* true
+
+Create an extra Behavior (Route) for /_next/data/ This route is used by Next.js to load data from the API Gateway on `getServerSideProps` calls.  The requests can end in `.json`, which would cause them to be routed to S3 if this route is not created.
+
+When false API routes with a period in the path will get routed to S3.
+
+When true API routes that contain /_next/data/ in the path will get routed to API Gateway
+even if they have a period in the path.
+
+---
+
 ##### `domainNameEdge`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsProps.domainNameEdge"></a>
 
 - *Type:* `string`
@@ -1125,7 +1170,19 @@ Additional edge lambda functions.
 - *Type:* `string`
 - *Default:* undefined
 
-Origin region that API Gateway will be deployed to, used for the config.yml on the Edge function to sign requests for the correct region.
+Origin region that API Gateway or Lambda function will be deployed to, used for the config.yml on the Edge function to sign requests for the correct region.
+
+---
+
+##### `originShieldRegion`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsProps.originShieldRegion"></a>
+
+- *Type:* `string`
+- *Default:* originRegion if specified, otherwise undefined
+
+Optional Origin Shield Region.
+
+This should be the region where the DynamoDB is located so the
+EdgeToOrigin calls have the lowest latency (~1 ms).
 
 ---
 
@@ -1337,6 +1394,18 @@ S3 logs bucket name.
 
 ---
 
+##### `originShieldRegion`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsS3Props.originShieldRegion"></a>
+
+- *Type:* `string`
+- *Default:* none
+
+Optional Origin Shield Region.
+
+This should be the region where the DynamoDB is located so the
+EdgeToOrigin calls have the lowest latency (~1 ms).
+
+---
+
 ##### `removalPolicy`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsS3Props.removalPolicy"></a>
 
 - *Type:* [`aws-cdk-lib.RemovalPolicy`](#aws-cdk-lib.RemovalPolicy)
@@ -1392,14 +1461,6 @@ S3 bucket for staged applications (prior to deploy).
 
 ---
 
-##### `httpApi`<sup>Required</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsSvcsProps.httpApi"></a>
-
-- *Type:* [`@aws-cdk/aws-apigatewayv2-alpha.HttpApi`](#@aws-cdk/aws-apigatewayv2-alpha.HttpApi)
-
-API Gateway v2 HTTP for Router and app.
-
----
-
 ##### `assetNameRoot`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsSvcsProps.assetNameRoot"></a>
 
 - *Type:* `string`
@@ -1418,6 +1479,14 @@ Optional asset name suffix.
 
 ---
 
+##### `httpApi`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsSvcsProps.httpApi"></a>
+
+- *Type:* [`@aws-cdk/aws-apigatewayv2-alpha.HttpApi`](#@aws-cdk/aws-apigatewayv2-alpha.HttpApi)
+
+API Gateway v2 HTTP for Router and app.
+
+---
+
 ##### `removalPolicy`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsSvcsProps.removalPolicy"></a>
 
 - *Type:* [`aws-cdk-lib.RemovalPolicy`](#aws-cdk-lib.RemovalPolicy)
@@ -1582,14 +1651,6 @@ Represents a MicroApps.
 
 #### Properties <a name="Properties"></a>
 
-##### `apigwy`<sup>Required</sup> <a name="@pwrdrvr/microapps-cdk.IMicroApps.apigwy"></a>
-
-- *Type:* [`@pwrdrvr/microapps-cdk.IMicroAppsAPIGwy`](#@pwrdrvr/microapps-cdk.IMicroAppsAPIGwy)
-
-{@inheritdoc IMicroAppsAPIGwy}.
-
----
-
 ##### `cf`<sup>Required</sup> <a name="@pwrdrvr/microapps-cdk.IMicroApps.cf"></a>
 
 - *Type:* [`@pwrdrvr/microapps-cdk.IMicroAppsCF`](#@pwrdrvr/microapps-cdk.IMicroAppsCF)
@@ -1614,6 +1675,14 @@ Represents a MicroApps.
 
 ---
 
+##### `apigwy`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.IMicroApps.apigwy"></a>
+
+- *Type:* [`@pwrdrvr/microapps-cdk.IMicroAppsAPIGwy`](#@pwrdrvr/microapps-cdk.IMicroAppsAPIGwy)
+
+{@inheritdoc IMicroAppsAPIGwy}.
+
+---
+
 ##### `edgeToOrigin`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.IMicroApps.edgeToOrigin"></a>
 
 - *Type:* [`@pwrdrvr/microapps-cdk.IMicroAppsEdgeToOrigin`](#@pwrdrvr/microapps-cdk.IMicroAppsEdgeToOrigin)
diff --git a/packages/microapps-cdk/src/MicroApps.ts b/packages/microapps-cdk/src/MicroApps.ts
index 8a5d1025..4f3d904d 100644
--- a/packages/microapps-cdk/src/MicroApps.ts
+++ b/packages/microapps-cdk/src/MicroApps.ts
@@ -178,6 +178,13 @@ export interface MicroAppsProps {
    */
   readonly rootPathPrefix?: string;
 
+  /**
+   * Create API Gateway for non-edge invocation
+   *
+   * @default false
+   */
+  readonly createAPIGateway?: boolean;
+
   /**
    * Create an extra Behavior (Route) for /api/ that allows
    * API routes to have a period in them.
@@ -191,6 +198,21 @@ export interface MicroAppsProps {
    */
   readonly createAPIPathRoute?: boolean;
 
+  /**
+   * Create an extra Behavior (Route) for /_next/data/
+   * This route is used by Next.js to load data from the API Gateway
+   * on `getServerSideProps` calls.  The requests can end in `.json`,
+   * which would cause them to be routed to S3 if this route is not created.
+   *
+   * When false API routes with a period in the path will get routed to S3.
+   *
+   * When true API routes that contain /_next/data/ in the path will get routed to API Gateway
+   * even if they have a period in the path.
+   *
+   * @default true
+   */
+  readonly createNextDataPathRoute?: boolean;
+
   /**
    * Adds an X-Forwarded-Host-Header when calling API Gateway
    *
@@ -228,7 +250,7 @@ export interface MicroAppsProps {
   readonly signingMode?: 'sign' | 'presign' | 'none';
 
   /**
-   * Origin region that API Gateway will be deployed to, used
+   * Origin region that API Gateway or Lambda function will be deployed to, used
    * for the config.yml on the Edge function to sign requests for
    * the correct region
    *
@@ -236,6 +258,16 @@ export interface MicroAppsProps {
    */
   readonly originRegion?: string;
 
+  /**
+   * Optional Origin Shield Region
+   *
+   * This should be the region where the DynamoDB is located so the
+   * EdgeToOrigin calls have the lowest latency (~1 ms).
+   *
+   * @default originRegion if specified, otherwise undefined
+   */
+  readonly originShieldRegion?: string;
+
   /**
    * Existing table for apps/versions/rules
    *
@@ -282,7 +314,7 @@ export interface IMicroApps {
   readonly svcs: IMicroAppsSvcs;
 
   /** {@inheritdoc IMicroAppsAPIGwy} */
-  readonly apigwy: IMicroAppsAPIGwy;
+  readonly apigwy?: IMicroAppsAPIGwy;
 }
 
 /**
@@ -321,8 +353,8 @@ export class MicroApps extends Construct implements IMicroApps {
     return this._s3;
   }
 
-  private _apigwy: MicroAppsAPIGwy;
-  public get apigwy(): IMicroAppsAPIGwy {
+  private _apigwy?: MicroAppsAPIGwy;
+  public get apigwy(): IMicroAppsAPIGwy | undefined {
     return this._apigwy;
   }
 
@@ -352,13 +384,16 @@ export class MicroApps extends Construct implements IMicroApps {
       s3PolicyBypassPrincipalARNs,
       s3StrictBucketPolicy,
       rootPathPrefix,
+      createAPIGateway = false,
       createAPIPathRoute = true,
+      createNextDataPathRoute = true,
       addXForwardedHostHeader = true,
       replaceHostHeader = true,
       signingMode = 'sign',
       originRegion,
       table,
       tableNameForEdgeToOrigin,
+      originShieldRegion = originRegion,
     } = props;
 
     this._s3 = new MicroAppsS3(this, 's3', {
@@ -370,20 +405,23 @@ export class MicroApps extends Construct implements IMicroApps {
         : undefined,
       assetNameRoot,
       assetNameSuffix,
+      originShieldRegion,
     });
-    this._apigwy = new MicroAppsAPIGwy(this, 'api', {
-      removalPolicy,
-      assetNameRoot,
-      assetNameSuffix,
-      domainNameEdge,
-      domainNameOrigin,
-      r53Zone,
-      certOrigin,
-      rootPathPrefix,
-      requireIAMAuthorization: signingMode !== 'none',
-    });
+    if (createAPIGateway) {
+      this._apigwy = new MicroAppsAPIGwy(this, 'api', {
+        removalPolicy,
+        assetNameRoot,
+        assetNameSuffix,
+        domainNameEdge,
+        domainNameOrigin,
+        r53Zone,
+        certOrigin,
+        rootPathPrefix,
+        requireIAMAuthorization: signingMode !== 'none',
+      });
+    }
     this._svcs = new MicroAppsSvcs(this, 'svcs', {
-      httpApi: this.apigwy.httpApi,
+      ...(this._apigwy ? { httpApi: this._apigwy.httpApi } : {}),
       removalPolicy,
       bucketApps: this._s3.bucketApps,
       bucketAppsOAI: this._s3.bucketAppsOAI,
@@ -406,6 +444,7 @@ export class MicroApps extends Construct implements IMicroApps {
         assetNameSuffix,
         removalPolicy,
         addXForwardedHostHeader,
+        setupApiGatewayPermissions: createAPIGateway,
         replaceHostHeader,
         originRegion,
         signingMode,
@@ -425,13 +464,15 @@ export class MicroApps extends Construct implements IMicroApps {
       assetNameSuffix,
       domainNameEdge,
       domainNameOrigin,
-      httpApi: this._apigwy.httpApi,
+      ...(this._apigwy ? { httpApi: this._apigwy.httpApi } : {}),
       r53Zone,
       certEdge,
       bucketAppsOrigin: this._s3.bucketAppsOrigin,
       bucketLogs: this._s3.bucketLogs,
       rootPathPrefix,
       createAPIPathRoute,
+      createNextDataPathRoute,
+      originShieldRegion,
       ...(edgeLambdas.length ? { edgeLambdas } : {}),
     });
   }
diff --git a/packages/microapps-cdk/src/MicroAppsCF.ts b/packages/microapps-cdk/src/MicroAppsCF.ts
index d452aa33..540d7cfc 100644
--- a/packages/microapps-cdk/src/MicroAppsCF.ts
+++ b/packages/microapps-cdk/src/MicroAppsCF.ts
@@ -62,7 +62,7 @@ export interface MicroAppsCFProps {
   /**
    * API Gateway v2 HTTP API for apps
    */
-  readonly httpApi: apigwy.HttpApi;
+  readonly httpApi?: apigwy.HttpApi;
 
   /**
    * Optional asset name root
@@ -106,7 +106,7 @@ export interface MicroAppsCFProps {
    * When true API routes that contain /api/ in the path will get routed to API Gateway
    * even if they have a period in the path.
    *
-   * @default true
+   * @default true if httpApi is provided
    */
   readonly createAPIPathRoute?: boolean;
 
@@ -121,7 +121,7 @@ export interface MicroAppsCFProps {
    * When true API routes that contain /_next/data/ in the path will get routed to API Gateway
    * even if they have a period in the path.
    *
-   * @default true
+   * @default true if httpApi is provided
    */
   readonly createNextDataPathRoute?: boolean;
 
@@ -131,6 +131,16 @@ export interface MicroAppsCFProps {
    * @default - no edge to API Gateway origin functions added
    */
   readonly edgeLambdas?: cf.EdgeLambda[];
+
+  /**
+   * Optional Origin Shield Region
+   *
+   * This should be the region where the DynamoDB is located so the
+   * EdgeToOrigin calls have the lowest latency (~1 ms).
+   *
+   * @default - none
+   */
+  readonly originShieldRegion?: string;
 }
 
 /**
@@ -166,9 +176,11 @@ export interface CreateAPIOriginPolicyOptions {
  */
 export interface AddRoutesOptions {
   /**
-   * API Gateway CloudFront Origin for API calls
+   * Default origin (invalid URL or API Gateway)
+   *
+   * @default invalid URL (never used)
    */
-  readonly apiGwyOrigin: cf.IOrigin;
+  readonly appOrigin: cf.IOrigin;
 
   /**
    * S3 Bucket CloudFront Origin for static assets
@@ -183,7 +195,7 @@ export interface AddRoutesOptions {
   /**
    * Origin Request policy for API Gateway Origin
    */
-  readonly apigwyOriginRequestPolicy: cf.IOriginRequestPolicy;
+  readonly appOriginRequestPolicy: cf.IOriginRequestPolicy;
 
   /**
    * Path prefix on the root of the CloudFront distribution
@@ -201,7 +213,7 @@ export interface AddRoutesOptions {
    * When true API routes that contain /api/ in the path will get routed to API Gateway
    * even if they have a period in the path.
    *
-   * @default true
+   * @default false
    */
   readonly createAPIPathRoute?: boolean;
 
@@ -216,7 +228,7 @@ export interface AddRoutesOptions {
    * When true API routes that contain /_next/data/ in the path will get routed to API Gateway
    * even if they have a period in the path.
    *
-   * @default true
+   * @default false
    */
   readonly createNextDataPathRoute?: boolean;
 
@@ -290,13 +302,13 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
    */
   public static addRoutes(_scope: Construct, props: AddRoutesOptions) {
     const {
-      apiGwyOrigin,
+      appOrigin: defaultOrigin,
       bucketAppsOrigin,
       distro,
-      apigwyOriginRequestPolicy,
+      appOriginRequestPolicy,
       rootPathPrefix = '',
-      createAPIPathRoute = true,
-      createNextDataPathRoute = true,
+      createAPIPathRoute = false,
+      createNextDataPathRoute = false,
     } = props;
 
     //
@@ -309,12 +321,12 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
       originRequestPolicy: cf.OriginRequestPolicy.CORS_S3_ORIGIN,
       viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
     };
-    const apiGwyBehaviorOptions: cf.AddBehaviorOptions = {
+    const appBehaviorOptions: cf.AddBehaviorOptions = {
       allowedMethods: cf.AllowedMethods.ALLOW_ALL,
       // TODO: Caching needs to be set by the app response
       cachePolicy: cf.CachePolicy.CACHING_DISABLED,
       compress: true,
-      originRequestPolicy: apigwyOriginRequestPolicy,
+      originRequestPolicy: appOriginRequestPolicy,
       viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
       edgeLambdas: props.edgeLambdas,
     };
@@ -327,14 +339,14 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
     if (createAPIPathRoute) {
       distro.addBehavior(
         posixPath.join(rootPathPrefix, '*/api/*'),
-        apiGwyOrigin,
-        apiGwyBehaviorOptions,
+        defaultOrigin,
+        appBehaviorOptions,
       );
 
       distro.addBehavior(
         posixPath.join(rootPathPrefix, 'api/*'),
-        apiGwyOrigin,
-        apiGwyBehaviorOptions,
+        defaultOrigin,
+        appBehaviorOptions,
       );
     }
 
@@ -349,8 +361,8 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
         // to the app origin as iframe-less will have no version before _next/data
         // in the path
         posixPath.join(rootPathPrefix, '*/_next/data/*'),
-        apiGwyOrigin,
-        apiGwyBehaviorOptions,
+        defaultOrigin,
+        appBehaviorOptions,
       );
 
       distro.addBehavior(
@@ -358,8 +370,8 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
         // to the app origin as iframe-less will have no version before _next/data
         // in the path
         posixPath.join(rootPathPrefix, '_next/data/*'),
-        apiGwyOrigin,
-        apiGwyBehaviorOptions,
+        defaultOrigin,
+        appBehaviorOptions,
       );
     }
 
@@ -386,7 +398,7 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
     // There is no trailing slash because Serverless Next.js wants
     // go load pages at /release/0.0.3 (with no trailing slash).
     //
-    distro.addBehavior(posixPath.join(rootPathPrefix, '/*'), apiGwyOrigin, apiGwyBehaviorOptions);
+    distro.addBehavior(posixPath.join(rootPathPrefix, '/*'), defaultOrigin, appBehaviorOptions);
   }
 
   private _cloudFrontDistro: cf.Distribution;
@@ -420,12 +432,13 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
       bucketLogs,
       bucketAppsOrigin,
       rootPathPrefix,
-      createAPIPathRoute = true,
-      createNextDataPathRoute = true,
+      createAPIPathRoute = !!props.httpApi,
+      createNextDataPathRoute = !!props.httpApi,
       edgeLambdas,
+      originShieldRegion,
     } = props;
 
-    const apigwyOriginRequestPolicy = MicroAppsCF.createAPIOriginPolicy(this, {
+    const appOriginRequestPolicy = MicroAppsCF.createAPIOriginPolicy(this, {
       assetNameRoot,
       assetNameSuffix,
       domainNameEdge,
@@ -437,7 +450,7 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
     let httpOriginFQDN: string = 'invalid.pwrdrvr.com';
     if (domainNameOrigin !== undefined) {
       httpOriginFQDN = domainNameOrigin;
-    } else {
+    } else if (httpApi) {
       httpOriginFQDN = `${httpApi.apiId}.execute-api.${Aws.REGION}.amazonaws.com`;
     }
 
@@ -448,10 +461,13 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
     //
     // CloudFront Distro
     //
-    const apiGwyOrigin = new cforigins.HttpOrigin(httpOriginFQDN, {
-      protocolPolicy: cf.OriginProtocolPolicy.HTTPS_ONLY,
-      originSslProtocols: [cf.OriginSslPolicy.TLS_V1_2],
-    });
+    const appOrigin = httpApi
+      ? new cforigins.HttpOrigin(httpOriginFQDN, {
+        protocolPolicy: cf.OriginProtocolPolicy.HTTPS_ONLY,
+        originSslProtocols: [cf.OriginSslPolicy.TLS_V1_2],
+        originShieldRegion,
+      })
+      : bucketAppsOrigin;
     this._cloudFrontDistro = new cf.Distribution(this, 'cft', {
       comment: assetNameRoot ? `${assetNameRoot}${assetNameSuffix}` : domainNameEdge,
       domainNames: domainNameEdge !== undefined ? [domainNameEdge] : undefined,
@@ -461,8 +477,8 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
         allowedMethods: cf.AllowedMethods.ALLOW_ALL,
         cachePolicy: cf.CachePolicy.CACHING_DISABLED,
         compress: true,
-        originRequestPolicy: apigwyOriginRequestPolicy,
-        origin: apiGwyOrigin,
+        originRequestPolicy: appOriginRequestPolicy,
+        origin: appOrigin,
         viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
         edgeLambdas,
       },
@@ -479,10 +495,10 @@ export class MicroAppsCF extends Construct implements IMicroAppsCF {
 
     // Add routes to the CloudFront Distribution
     MicroAppsCF.addRoutes(scope, {
-      apiGwyOrigin,
+      appOrigin,
       bucketAppsOrigin,
       distro: this._cloudFrontDistro,
-      apigwyOriginRequestPolicy: apigwyOriginRequestPolicy,
+      appOriginRequestPolicy,
       rootPathPrefix,
       createAPIPathRoute,
       createNextDataPathRoute,
diff --git a/packages/microapps-cdk/src/MicroAppsEdgeToOrigin.ts b/packages/microapps-cdk/src/MicroAppsEdgeToOrigin.ts
index a8e46884..8336899a 100644
--- a/packages/microapps-cdk/src/MicroAppsEdgeToOrigin.ts
+++ b/packages/microapps-cdk/src/MicroAppsEdgeToOrigin.ts
@@ -117,6 +117,13 @@ export interface MicroAppsEdgeToOriginProps {
    * Implies that 2nd generation routing is enabled.
    */
   readonly tableRulesArn?: string;
+
+  /**
+   * Enable invoking API Gateway from the Edge Lambda
+   *
+   * @default false
+   */
+  readonly setupApiGatewayPermissions?: boolean;
 }
 
 export interface GenerateEdgeToOriginConfigOptions {
@@ -168,6 +175,7 @@ ${props.rootPathPrefix ? `rootPathPrefix: '${props.rootPathPrefix}'` : ''}`;
       assetNameRoot,
       assetNameSuffix,
       originRegion,
+      setupApiGatewayPermissions = false,
       signingMode = 'sign',
       removalPolicy,
       rootPathPrefix,
@@ -206,18 +214,22 @@ ${props.rootPathPrefix ? `rootPathPrefix: '${props.rootPathPrefix}'` : ''}`;
         // to invoke any API Gateway API that we apply a tag to
         // We allow the edge function to sign for all regions since
         // we may use custom closest region in the future.
-        new iam.PolicyStatement({
-          actions: ['execute-api:Invoke'],
-          resources: [`arn:aws:execute-api:*:${Aws.ACCOUNT_ID}:*/*/*/*`],
-          // Unfortunately, API Gateway access cannot be restricted using
-          // tags on the target resource
-          // https://docs.aws.amazon.com/IAM/latest/UserGuide/access_tags.html
-          // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html#networking_svcs
-          // conditions: {
-          //   // TODO: Set this to a string unique to each stack
-          //   StringEquals: { 'aws:ResourceTag/microapp-managed': 'true' },
-          // },
-        }),
+        ...(setupApiGatewayPermissions
+          ? [
+              new iam.PolicyStatement({
+                actions: ['execute-api:Invoke'],
+                resources: [`arn:aws:execute-api:*:${Aws.ACCOUNT_ID}:*/*/*/*`],
+                // Unfortunately, API Gateway access cannot be restricted using
+                // tags on the target resource
+                // https://docs.aws.amazon.com/IAM/latest/UserGuide/access_tags.html
+                // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html#networking_svcs
+                // conditions: {
+                //   // TODO: Set this to a string unique to each stack
+                //   StringEquals: { 'aws:ResourceTag/microapp-managed': 'true' },
+                // },
+              }),
+            ]
+          : []),
         //
         // Grant permission to invoke tagged Function URLs
         //
diff --git a/packages/microapps-cdk/src/MicroAppsS3.ts b/packages/microapps-cdk/src/MicroAppsS3.ts
index 3982f16b..980084b0 100644
--- a/packages/microapps-cdk/src/MicroAppsS3.ts
+++ b/packages/microapps-cdk/src/MicroAppsS3.ts
@@ -83,6 +83,16 @@ export interface MicroAppsS3Props {
    * @default none
    */
   readonly assetNameSuffix?: string;
+
+  /**
+   * Optional Origin Shield Region
+   *
+   * This should be the region where the DynamoDB is located so the
+   * EdgeToOrigin calls have the lowest latency (~1 ms).
+   *
+   * @default - none
+   */
+  readonly originShieldRegion?: string;
 }
 
 /**
@@ -124,7 +134,7 @@ export class MicroAppsS3 extends Construct implements IMicroAppsS3 {
       throw new Error('props must be set');
     }
 
-    const { removalPolicy, assetNameRoot, assetNameSuffix } = props;
+    const { removalPolicy, assetNameRoot, assetNameSuffix, originShieldRegion } = props;
 
     // Use Auto-Delete S3Bucket if removal policy is DESTROY
     const s3AutoDeleteItems = removalPolicy === RemovalPolicy.DESTROY;
@@ -163,6 +173,7 @@ export class MicroAppsS3 extends Construct implements IMicroAppsS3 {
     // Add Origin for CloudFront
     this._bucketAppsOrigin = new cforigins.S3Origin(this._bucketApps, {
       originAccessIdentity: this.bucketAppsOAI,
+      originShieldRegion,
     });
   }
 }
diff --git a/packages/microapps-cdk/src/MicroAppsSvcs.ts b/packages/microapps-cdk/src/MicroAppsSvcs.ts
index ebc735ac..447b7fef 100644
--- a/packages/microapps-cdk/src/MicroAppsSvcs.ts
+++ b/packages/microapps-cdk/src/MicroAppsSvcs.ts
@@ -44,7 +44,7 @@ export interface MicroAppsSvcsProps {
   /**
    * API Gateway v2 HTTP for Router and app
    */
-  readonly httpApi: apigwy.HttpApi;
+  readonly httpApi?: apigwy.HttpApi;
 
   /**
    * Application environment, passed as `NODE_ENV`
@@ -359,7 +359,7 @@ export class MicroAppsSvcs extends Construct implements IMicroAppsSvcs {
       timeout: Duration.seconds(15),
       environment: {
         NODE_ENV: appEnv,
-        APIGWY_ID: httpApi.httpApiId,
+        ...(httpApi ? { APIGWY_ID: httpApi.httpApiId } : {}),
         DATABASE_TABLE_NAME: this._table.tableName,
         FILESTORE_STAGING_BUCKET: bucketAppsStaging.bucketName,
         FILESTORE_DEST_BUCKET: bucketApps.bucketName,
@@ -598,19 +598,23 @@ export class MicroAppsSvcs extends Construct implements IMicroAppsSvcs {
       resources: [`arn:aws:apigateway:${Aws.REGION}::/apis`],
     });
     this._deployerFunc.addToRolePolicy(policyAPIList);
-    // Grant full control over the API we created
-    const policyAPIManage = new iam.PolicyStatement({
-      effect: iam.Effect.ALLOW,
-      actions: ['apigateway:*'],
-      resources: [
-        `arn:aws:apigateway:${Aws.REGION}:${Aws.ACCOUNT_ID}:${httpApi.httpApiId}/*`,
-        `arn:aws:apigateway:${Aws.REGION}::/apis/${httpApi.httpApiId}/integrations/*`,
-        `arn:aws:apigateway:${Aws.REGION}::/apis/${httpApi.httpApiId}/integrations`,
-        `arn:aws:apigateway:${Aws.REGION}::/apis/${httpApi.httpApiId}/routes`,
-        `arn:aws:apigateway:${Aws.REGION}::/apis/${httpApi.httpApiId}/routes/*`,
-      ],
-    });
-    this._deployerFunc.addToRolePolicy(policyAPIManage);
+
+    if (httpApi) {
+      // Grant full control over the API we created
+      const policyAPIManage = new iam.PolicyStatement({
+        effect: iam.Effect.ALLOW,
+        actions: ['apigateway:*'],
+        resources: [
+          `arn:aws:apigateway:${Aws.REGION}:${Aws.ACCOUNT_ID}:${httpApi.httpApiId}/*`,
+          `arn:aws:apigateway:${Aws.REGION}::/apis/${httpApi.httpApiId}/integrations/*`,
+          `arn:aws:apigateway:${Aws.REGION}::/apis/${httpApi.httpApiId}/integrations`,
+          `arn:aws:apigateway:${Aws.REGION}::/apis/${httpApi.httpApiId}/routes`,
+          `arn:aws:apigateway:${Aws.REGION}::/apis/${httpApi.httpApiId}/routes/*`,
+        ],
+      });
+      this._deployerFunc.addToRolePolicy(policyAPIManage);
+    }
+
     // Grant full control over lambdas that indicate they are microapps
     const policyAPIManageLambdas = new iam.PolicyStatement({
       effect: iam.Effect.ALLOW,
@@ -625,26 +629,28 @@ export class MicroAppsSvcs extends Construct implements IMicroAppsSvcs {
     });
     this._deployerFunc.addToRolePolicy(policyAPIManageLambdas);
 
-    // This creates an integration and a router
-    const route = new apigwy.HttpRoute(this, 'route-default', {
-      httpApi,
-      routeKey: apigwy.HttpRouteKey.DEFAULT,
-      integration: new apigwyint.HttpLambdaIntegration('router-integration', routerAlias),
-      authorizer: requireIAMAuthorization ? new apigwyAuth.HttpIamAuthorizer() : undefined,
-    });
+    if (httpApi) {
+      // This creates an integration and a router
+      const route = new apigwy.HttpRoute(this, 'route-default', {
+        httpApi,
+        routeKey: apigwy.HttpRouteKey.DEFAULT,
+        integration: new apigwyint.HttpLambdaIntegration('router-integration', routerAlias),
+        authorizer: requireIAMAuthorization ? new apigwyAuth.HttpIamAuthorizer() : undefined,
+      });
 
-    let routeArn = route.routeArn;
-    // Remove the trailing `/` on the ARN, which is not correct
-    if (routeArn.endsWith('/')) {
-      routeArn = routeArn.slice(0, routeArn.length - 1);
-    }
+      let routeArn = route.routeArn;
+      // Remove the trailing `/` on the ARN, which is not correct
+      if (routeArn.endsWith('/')) {
+        routeArn = routeArn.slice(0, routeArn.length - 1);
+      }
 
-    // Grant API Gateway permission to invoke the Lambda
-    new lambda.CfnPermission(this, 'router-invoke', {
-      action: 'lambda:InvokeFunction',
-      functionName: this._routerFunc.functionName,
-      principal: 'apigateway.amazonaws.com',
-      sourceArn: routeArn,
-    });
+      // Grant API Gateway permission to invoke the Lambda
+      new lambda.CfnPermission(this, 'router-invoke', {
+        action: 'lambda:InvokeFunction',
+        functionName: this._routerFunc.functionName,
+        principal: 'apigateway.amazonaws.com',
+        sourceArn: routeArn,
+      });
+    }
   }
 }
diff --git a/packages/microapps-edge-to-origin/src/index.ts b/packages/microapps-edge-to-origin/src/index.ts
index 34da3cb4..72c5977d 100644
--- a/packages/microapps-edge-to-origin/src/index.ts
+++ b/packages/microapps-edge-to-origin/src/index.ts
@@ -203,14 +203,28 @@ export const handler: lambda.CloudFrontRequestHandler = async (
     }
 
     // Overwrite the origin
+    // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html#lambda-examples-content-based-routing-examples
     if (request.origin?.custom?.domainName) {
       request.origin = { ...request.origin };
       request.origin.custom.domainName = originHost;
-
-      // Remove the appver query string to avoid problems with some frameworks
-      request.querystring = (request.querystring ?? '').replace(/&?appver=[^&]*/, '');
+    } else {
+      request.origin = {
+        custom: {
+          domainName: originHost,
+          keepaliveTimeout: 5,
+          port: 443,
+          protocol: 'https',
+          readTimeout: 30,
+          sslProtocols: ['TLSv1.2'],
+          customHeaders: {},
+          path: '',
+        },
+      };
     }
 
+    // Remove the appver query string to avoid problems with some frameworks
+    request.querystring = (request.querystring ?? '').replace(/&?appver=[^&]*/, '');
+
     // Lambda Function URLs cannot have a custom domain name
     // Function URLs will always contain `.lambda-url.`
     // API Gateway URLs can contain '.execute-api.' but will not
diff --git a/packages/microapps-edge-to-origin/src/sign-request.ts b/packages/microapps-edge-to-origin/src/sign-request.ts
index 4461b26d..cd254f24 100644
--- a/packages/microapps-edge-to-origin/src/sign-request.ts
+++ b/packages/microapps-edge-to-origin/src/sign-request.ts
@@ -2,6 +2,8 @@ import type * as lambda from 'aws-lambda';
 import { SignatureV4 } from '@aws-sdk/signature-v4';
 import { cloudfrontToSignableRequest } from './translate-request';
 
+const sigHeaders = ['authorization', 'x-amz-date', 'x-amz-security-token', 'x-amz-content-sha256'];
+
 /**
  * Sign (headers) a request with AWS Signature V4
  * @param request
@@ -16,15 +18,15 @@ export async function signRequest(
 ): Promise<lambda.CloudFrontRequest> {
   const httpRequest = cloudfrontToSignableRequest({ request });
 
+  // Remove SigV4 headers before we sign because we will overwrite them
+  for (const key of sigHeaders) {
+    delete httpRequest.headers[key];
+  }
+
   const signedRequest = await signer.sign(httpRequest);
 
   // Copy the signature headers into the request to forward to origin
-  for (const key of [
-    'authorization',
-    'x-amz-date',
-    'x-amz-security-token',
-    'x-amz-content-sha256',
-  ]) {
+  for (const key of sigHeaders) {
     request.headers[key] = [{ key: key, value: signedRequest.headers[key] }];
   }
 
@@ -48,12 +50,7 @@ export async function presignRequest(
   const signedRequest = await signer.presign(httpRequest);
 
   // Copy the signature headers into the request to forward to origin
-  for (const key of [
-    'authorization',
-    'x-amz-date',
-    'x-amz-security-token',
-    'x-amz-content-sha256',
-  ]) {
+  for (const key of sigHeaders) {
     request.headers[key] = [{ key: key, value: signedRequest.headers[key] }];
   }
 


Test patch:
diff --git a/packages/microapps-cdk/test/MicroApps.test.ts b/packages/microapps-cdk/test/MicroApps.test.ts
index 81604d3a..168bdef4 100644
--- a/packages/microapps-cdk/test/MicroApps.test.ts
+++ b/packages/microapps-cdk/test/MicroApps.test.ts
@@ -19,7 +19,7 @@ describe('MicroApps', () => {
     });
 
     expect(construct).toBeDefined();
-    expect(construct.apigwy).toBeDefined();
+    expect(construct.apigwy).not.toBeDefined();
     expect(construct.edgeToOrigin).toBeDefined();
     expect(construct.cf).toBeDefined();
     expect(construct.s3).toBeDefined();
@@ -33,9 +33,6 @@ describe('MicroApps', () => {
     // Confirm that logical IDs have not changed accidentally (causes delete/create)
     Template.fromStack(stack).templateMatches({
       Resources: {
-        constructapigwy894904EC: {
-          Type: 'AWS::ApiGatewayV2::Api',
-        },
         constructcft0A8410EA: {
           Type: 'AWS::CloudFront::Distribution',
         },
@@ -84,7 +81,7 @@ describe('MicroApps', () => {
     });
 
     expect(construct).toBeDefined();
-    expect(construct.apigwy).toBeDefined();
+    expect(construct.apigwy).not.toBeDefined();
     expect(construct.edgeToOrigin).toBeDefined();
     expect(construct.cf).toBeDefined();
     expect(construct.s3).toBeDefined();
@@ -98,9 +95,6 @@ describe('MicroApps', () => {
     // Confirm that logical IDs have not changed accidentally (causes delete/create)
     Template.fromStack(stack).templateMatches({
       Resources: {
-        constructapigwy894904EC: {
-          Type: 'AWS::ApiGatewayV2::Api',
-        },
         constructcft0A8410EA: {
           Type: 'AWS::CloudFront::Distribution',
         },
@@ -150,7 +144,7 @@ describe('MicroApps', () => {
     });
 
     expect(construct).toBeDefined();
-    expect(construct.apigwy).toBeDefined();
+    expect(construct.apigwy).not.toBeDefined();
     expect(construct.edgeToOrigin).toBeDefined();
     expect(construct.cf).toBeDefined();
     expect(construct.s3).toBeDefined();
@@ -170,9 +164,6 @@ describe('MicroApps', () => {
     // Confirm that logical IDs have not changed accidentally (causes delete/create)
     Template.fromStack(stack).templateMatches({
       Resources: {
-        constructapigwy894904EC: {
-          Type: 'AWS::ApiGatewayV2::Api',
-        },
         constructcft0A8410EA: {
           Type: 'AWS::CloudFront::Distribution',
         },
@@ -223,7 +214,7 @@ describe('MicroApps', () => {
     });
 
     expect(construct).toBeDefined();
-    expect(construct.apigwy).toBeDefined();
+    expect(construct.apigwy).not.toBeDefined();
     expect(construct.edgeToOrigin).toBeDefined();
     expect(construct.cf).toBeDefined();
     expect(construct.s3).toBeDefined();
@@ -254,9 +245,6 @@ describe('MicroApps', () => {
     // Confirm that logical IDs have not changed accidentally (causes delete/create)
     Template.fromStack(stack).templateMatches({
       Resources: {
-        constructapigwy894904EC: {
-          Type: 'AWS::ApiGatewayV2::Api',
-        },
         constructcft0A8410EA: {
           Type: 'AWS::CloudFront::Distribution',
         },
@@ -315,7 +303,7 @@ describe('MicroApps', () => {
     });
 
     expect(construct).toBeDefined();
-    expect(construct.apigwy).toBeDefined();
+    expect(construct.apigwy).not.toBeDefined();
     expect(construct.cf).toBeDefined();
     expect(construct.s3).toBeDefined();
     expect(construct.svcs).toBeDefined();
@@ -328,9 +316,6 @@ describe('MicroApps', () => {
     // Confirm that logical IDs have not changed accidentally (causes delete/create)
     Template.fromStack(stack).templateMatches({
       Resources: {
-        constructapigwy894904EC: {
-          Type: 'AWS::ApiGatewayV2::Api',
-        },
         constructcft0A8410EA: {
           Type: 'AWS::CloudFront::Distribution',
         },