OpenNextJS-AWS

repository·main·Indexed 26 days ago

https://github.com/opennextjs/opennextjs-aws

An end-to-end integration testing suite and adapter for validating Next.js features on AWS. It includes tools for configuring OpenNext via open-next.config.ts, managing infrastructure overrides for S3, DynamoDB, and SQS, and deploying router implementations (App, Pages, or combined) using SST.

Tokens
5.7K
Snippets
14
Records
34
Agent score
88%

What's inside opennextjs-aws

  1. Run End-to-End (e2e) integration tests

    main

    To run the e2e testing suite against a deployed application, you must first deploy the example application using SST, export the resulting URLs from the SST outputs, and then execute the test command from the packages/tests-e2e directory.

    Note: The test suite covers three permutations:

    1. App Router project: located in tests/appDirOnly and examples/app-router.
    2. Pages Router project: located in tests/pagesOnly and examples/pages-router.
    3. App + Pages Router project: located in tests/appDirAndPages and examples/app-pages-router.
    # 1. Deploy the app
    cd examples/sst
    npx sst deploy --stage e2e
    
    # 2. Export the URLs
    export APP_ROUTER_URL=$(jq -r '.["e2e-example-AppRouter"].url' .sst/outputs.json)
    export PAGES_ROUTER_URL=$(jq -r '.["e2e-example-PagesRouter"].url' .sst/outputs.json)
    export APP_PAGES_ROUTER_URL=$(jq -r '.["e2e-example-AppPagesRouter"].url' .sst/outputs.json)
    
    # 3. Run the test
    cd ../../packages/tests-e2e
    pnpm run e2e:dev
  2. Resolve OpenNext overrides via factory functions

    main
    OpenNext allows you to provide custom implementations for core components (like converters, wrappers, caches, and loaders) through the configuration. If you provide a function for these options, OpenNext will call that function to resolve the implementation. If you provide a non-function value (or leave it as default), OpenNext resolves a built-in default implementation (e.g., S3 for incremental cache, DynamoDB for tag cache, or SQS for queues).
  3. Avoid including types in #override and #imports blocks

    main

    When using #override or #imports directives, do not include import type statements inside these blocks. esbuild will remove the preceding comments (e.g., //#override id) during the build process, which can break the directive.

    Instead, place all import type statements outside of the directive blocks. Since types are removed in the final output, this ensures the directives remain intact and functional.

    import type { PluginHandler } from "../next-types.js";
    import type { IncomingMessage } from "../request.js";
    import type { ServerResponse } from "../response.js";
    
    //#override imports
    import { requestHandler } from "./util.js";
    //#endOverride
  4. Troubleshoot ISR testing in e2e suite

    main
    When testing Incremental Static Regeneration (ISR) via isr.test.ts, be aware that running in next dev mode prevents caching. This causes each reload to return a new timestamp, which may break tests. To ensure ISR caching behavior is correctly tested, you must use next build followed by next start instead of the development server.
  5. Configure OpenNext with open-next.config.ts

    main

    OpenNext can be configured using an open-next.config.ts file. This file allows you to define global overrides for infrastructure components, specific function configurations, and dangerous experimental flags.

    Key configuration sections include:

    • default.override: Specifies which infrastructure implementations to use for core components like the wrapper, queue, incremental cache, and tag cache.
    • functions: Allows for per-function configuration overrides.
    • dangerous: Contains experimental or high-impact settings, such as middlewareHeadersOverrideNextConfigHeaders.
    • buildCommand: Defines the command used to build the Next.js application.
    const config = {
      default: {
        override: {
          wrapper: "aws-lambda-streaming",
          queue: "sqs-lite",
          incrementalCache: "s3-lite",
          tagCache: "dynamodb-lite",
        },
      },
      functions: {},
      dangerous: {
        middlewareHeadersOverrideNextConfigHeaders: true,
      },
      buildCommand: "npx turbo build",
    };
    
    export default config;
  6. Configure Function Installation Options

    main

    When deploying functions, you can use the install option to specify additional packages to be installed in the function environment (e.g., sharp for image optimization). You can also specify the architecture, node version, and libc type.

    export interface InstallOptions {
      /**
       * List of packages to install
       * @example
       * ```ts
       * install: {
       *  packages: ["sharp@0.32"]
       * }
       * ```
       */
      packages: string[];
      arch?: "x64" | "arm64";
      nodeVersion?: string;
      libc?: "glibc" | "musl";
      os?: string;
      additionalArgs?: string;
    }
  7. Configure SplittedFunctionOptions

    main

    Use SplittedFunctionOptions to define specific configurations for individual routes when using the splitting feature. You must specify which routes to include using the routes array with RouteTemplate formats (e.g., app/api/test/route or pages/admin). You can also define CloudFront-compatible patterns (e.g., /api/*).

    export interface SplittedFunctionOptions extends FunctionOptions {
      /**
       * Here you should specify all the routes you want to use. 
       * For app routes, you should use the `app/${name}/route` format or `app/${name}/page` for pages. 
       * For pages, you should use the `page/${name}` format.
       */
      routes: RouteTemplate[];
      /**
       * Cloudfront compatible patterns.
       * i.e. /api/*
       * @default []
       */
      patterns: string[];
    }
  8. Configure Lambda function routes and patterns

    main

    Within the functions object in your open-next.config.ts, you can define specific configurations for different types of functions (e.g., api).

    • routes: An array of specific file paths that should be treated as standalone functions.
    • patterns: An array of glob patterns (e.g., /api/*) used to match incoming requests to specific functions.
    functions: {
      api: {
        routes: ["app/api/client/route", "app/api/host/route", "pages/api/hello"],
        patterns: ["/api/*"],
      },
    },
  9. Configure Middleware (External vs Internal)

    main

    OpenNext supports two types of middleware configuration:

    1. External Middleware (external: true): The middleware is deployed separately. It can run on node or edge runtimes. It includes an originResolver to resolve origins for internal rewrites (defaults to pattern-env).
    2. Internal Middleware (external: false): The middleware is part of the main function deployment.

    Both types support an assetResolver to resolve assets in the routing layer.

    export type ExternalMiddlewareConfig = DefaultFunctionOptions & CommonMiddlewareConfig & {
      external: true;
      runtime?: "node" | "edge";
      override?: OverrideOptions;
      originResolver?: IncludedOriginResolver | LazyLoadedOverride<OriginResolver>;
    };
    
    export type InternalMiddlewareConfig = {
      external: false;
    } & CommonMiddlewareConfig;
  10. Configure OpenNextConfig

    main

    The OpenNextConfig interface is the primary configuration object for OpenNext. It allows you to define settings for the default function, specific splitted functions, middleware, warmer, revalidation, and image optimization. You can also customize the build command, build output path, and app path.

    export interface OpenNextConfig {
      default: FunctionOptions;
      functions?: Record<string, SplittedFunctionOptions>;
      middleware?: ExternalMiddlewareConfig | InternalMiddlewareConfig;
      warmer?: DefaultFunctionOptions<WarmerEvent, WarmerResponse> & { invokeFunction?: IncludedWarmer | LazyLoadedOverride<Warmer>; };
      revalidate?: DefaultFunctionOptions<{ host: string; url: string; type: "revalidate" }, { type: "revalidate" }>;
      imageOptimization?: DefaultFunctionOptions & { loader?: IncludedImageLoader | LazyLoadedOverride<ImageLoader>; };
      initializationFunction?: DefaultFunctionOptions & { tagCache?: IncludedTagCache | LazyLoadedOverride<TagCache>; };
      dangerous?: DangerousOptions;
      buildCommand?: string;
      buildOutputPath?: string;
      appPath?: string;
      packageJsonPath?: string;
      edgeExternals?: string[];
    }