aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.41k stars 2.11k forks source link

Amplify.configure has 700ms impact on Next.js SSR first page load #8918

Closed dryna closed 1 year ago

dryna commented 2 years ago

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

Authentication, GraphQL API

Amplify Categories

auth, api

Environment information

``` System: OS: macOS 11.6 CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz Memory: 641.36 MB / 32.00 GB Shell: 5.8 - /bin/zsh Binaries: Node: 16.5.0 - ~/.nvm/versions/node/v16.5.0/bin/node Yarn: 1.22.4 - /usr/local/bin/yarn npm: 7.19.1 - ~/.nvm/versions/node/v16.5.0/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Browsers: Chrome: 93.0.4577.82 Firefox: 89.0.2 npmPackages: @ampproject/toolbox-optimizer: undefined () @aws-amplify/ui-react: ^1.2.16 => 1.2.16 @babel/core: undefined () @types/react: ^17.0.4 => 17.0.21 @vercel/nft: undefined () amphtml-validator: undefined () arg: undefined () async-retry: undefined () async-sema: undefined () aws-amplify: ^4.2.10 => 4.2.10 bfj: undefined () cacache: undefined () ci-info: undefined () cli-select: undefined () comment-json: undefined () compression: undefined () conf: undefined () content-type: undefined () cookie: undefined () cross-spawn: undefined () css-loader: undefined () debug: undefined () devalue: undefined () escape-string-regexp: undefined () file-loader: undefined () find-cache-dir: undefined () find-up: undefined () fresh: undefined () glob: undefined () gzip-size: undefined () http-proxy: undefined () ignore-loader: undefined () is-animated: undefined () is-docker: undefined () is-wsl: undefined () json5: undefined () jsonwebtoken: undefined () loader-utils: undefined () lodash.curry: undefined () lru-cache: undefined () mini-css-extract-plugin: undefined () nanoid: undefined () neo-async: undefined () next: 11.1.2 => 11.1.2 ora: undefined () postcss-flexbugs-fixes: undefined () postcss-loader: undefined () postcss-preset-env: undefined () postcss-scss: undefined () react: 17.0.2 => 17.0.2 react-dom: 17.0.2 => 17.0.2 recast: undefined () resolve-url-loader: undefined () sass-loader: undefined () schema-utils: undefined () semver: undefined () send: undefined () source-map: undefined () string-hash: undefined () strip-ansi: undefined () terser: undefined () text-table: undefined () typescript: ^4.2.4 => 4.4.3 unistore: undefined () web-vitals: undefined () webpack: undefined () webpack-sources: undefined () zen-observable: undefined () npmGlobalPackages: npm: 7.19.1 vercel: 23.1.2 ```

Describe the bug

Adding Amplify.configure({ ...awsExports, ssr: true }); to the project makes all the SSR pages to get around 900 ms additional load time on the first load. This is not a problem with deployment with containers because any following load is without this issue. However when using lambdas to deploy any new start of the lambda has around 900 ms invocation time + some time of the cold start. Especially if the page dosen't have yet a lot of traffic this is an issue.

Expected behavior

I would expect for Amplify.configure({ ...awsExports, ssr: true }); not to have such impact on next pages load time or if using Amplify.configure({ ...awsExports, ssr: false }); to be able to turn it off.

From the amplify hosting behaviour I would expect for the edge function for the first invocation time to be much faster. For the pages even with empty getServerSideProps function I'm getting invocation time around 1 - 3 seconds.

For now only solution I have is to use containers deployment (Fargate) until our web application won't get enough users to not experience lambda "first invocation" (the cold start is not a problem it's around 150ms). The other solution would be for amplify/aws to support provisioning edge functions.

Reproduction steps

  1. Create sample next.js app
  2. Add amplify to it. On the page add Amplify.configure({ ...awsExports, ssr: true });
  3. Add empty getServerSideProps function
  4. Doyarn build & yarn start
  5. First open of the page after start of the next.js server will have around 900 ms server response time.

The same thing can be reproduce using amplify hosting, vercel or https://github.com/serverless-nextjs/serverless-next.js to deploy the app. The response times only get bigger up to around 1,4 second.

Code Snippet

Page with SSR and amplify.configure

import { AmplifyAuthenticator, withAuthenticator, AmplifySignOut } from "@aws-amplify/ui-react";
import { Amplify, API, Auth, withSSRContext } from "aws-amplify";
import Head from "next/head";
import { useEffect } from "react";
import awsExports from "../src/aws-exports";
import { createTask } from "../src/graphql/mutations";
import { listTasks } from "../src/graphql/queries";
import styles from "./../styles/Home.module.css";

Amplify.configure({ ...awsExports, ssr: true });

export async function getServerSideProps({ req }) {
  return {
    props: {
      tasks: [],
    },
  };
}

async function handleCreateTask(event) {
  event.preventDefault();
}

const Home = ({ tasks = [] }) => {
  return (
    <div className={styles.container}>
      <Head>
        <title>Amplify + Next.js</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>Amplify + Next.js</h1>

        <p className={styles.description}>
          <code className={styles.code}>{tasks.length}</code>
          tasks
        </p>

        <div className={styles.grid}>
          {tasks.map((task) => (
            <a className={styles.card} href={`/tasks/${task.id}`} key={task.id}>
              <h3>{task.title}</h3>
              <p>{task.description}</p>
            </a>
          ))}

          <div className={styles.card}>
            <h3 className={styles.title}>New Task</h3>

            {/* <AmplifyAuthenticator> */}
              <form onSubmit={handleCreateTask}>
                <fieldset>
                  <legend>Title</legend>
                  <input
                    defaultValue={`Today, ${new Date().toLocaleTimeString()}`}
                    name="title"
                  />
                </fieldset>

                <fieldset>
                  <legend>description</legend>
                  <textarea
                    defaultValue="I built an Amplify app with Next.js!"
                    name="description"
                  />
                </fieldset>

                <button>Create Task</button>
                <button type="button" >
                  Sign out
                </button>
              </form>
          </div>
        </div>
      </main>
    </div>
  );
}

export default Home;

Page to compare without Amplify.configure

// import { AmplifyAuthenticator, withAuthenticator, AmplifySignOut } from "@aws-amplify/ui-react";
// import { Amplify, API, Auth, withSSRContext } from "aws-amplify";
import Head from "next/head";
// import { useEffect } from "react";
// import awsExports from "../src/aws-exports";
// import { createTask } from "../src/graphql/mutations";
// import { listTasks } from "../src/graphql/queries";
import styles from "./../styles/Home.module.css";

// Amplify.configure({ ...awsExports, ssr: true });

export async function getServerSideProps({ req }) {
  return {
    props: {
      tasks: [],
    },
  };
}

async function handleCreateTask(event) {
  event.preventDefault();
}

const Home = ({ tasks = [] }) => {
  return (
    <div className={styles.container}>
      <Head>
        <title>Amplify + Next.js</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>Amplify + Next.js</h1>

        <p className={styles.description}>
          <code className={styles.code}>{tasks.length}</code>
          tasks
        </p>

        <div className={styles.grid}>
          {tasks.map((task) => (
            <a className={styles.card} href={`/tasks/${task.id}`} key={task.id}>
              <h3>{task.title}</h3>
              <p>{task.description}</p>
            </a>
          ))}

          <div className={styles.card}>
            <h3 className={styles.title}>New Task</h3>

            {/* <AmplifyAuthenticator> */}
              <form onSubmit={handleCreateTask}>
                <fieldset>
                  <legend>Title</legend>
                  <input
                    defaultValue={`Today, ${new Date().toLocaleTimeString()}`}
                    name="title"
                  />
                </fieldset>

                <fieldset>
                  <legend>description</legend>
                  <textarea
                    defaultValue="I built an Amplify app with Next.js!"
                    name="description"
                  />
                </fieldset>

                <button>Create Task</button>
                <button type="button" >
                  Sign out
                </button>
              </form>
          </div>
        </div>
      </main>
    </div>
  );
}

export default Home;

Log output

``` // Put your logs below this line ```

aws-exports.js

const awsmobile = {
    "aws_project_region": "eu-west-1",
    "aws_cognito_identity_pool_id": "eu-west-1:xxx",
    "aws_cognito_region": "eu-west-1",
    "aws_user_pools_id": "eu-west-1_xxx",
    "aws_user_pools_web_client_id": "xxx",
    "oauth": {},
    "aws_appsync_graphqlEndpoint": "http://192.168.86.88:20002/graphql",
    "aws_appsync_region": "eu-west-1",
    "aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS",
    "aws_appsync_apiKey": "da2-fakeApiIdxxx",
    "aws_appsync_dangerously_connect_to_http_endpoint_for_testing": true
};

export default awsmobile;

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

package.json

{
  "name": "next-amplified",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@aws-amplify/ui-react": "^1.2.16",
    "aws-amplify": "^4.2.10",
    "next": "11.1.2",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@types/react": "^17.0.4",
    "typescript": "^4.2.4"
  }
}

I've tested the same behaviour with Next.js 10.x and with aws-amplify 3.x.

chrisbonifacio commented 2 years ago

Hi @dryna 👋 do you have a time to first page load with SSR disabled for comparison in my own testing?

dryna commented 2 years ago

Without SSR (commenting out getServerSideProps) the line Amplify.configure({ ...awsExports, ssr: false }); dosen't have any influence. Then first load after starting the server takes around 22ms. To test I'm doing yarn build and then yarn start.

I will check also later one the first page load time for static pages when hosting on amplify hosting, because i think it might be another topic.

nadetastic commented 1 year ago

Closing this issue in favor of aws-amplify/amplify-hosting#3855