May 7, 2023 • ☕️ 4 min read
日本語

Problem

Indeed, Next.js is characterized by its Server-Side Rendering (SSR) feature. When using SSR, deploying on Vercel often becomes the standard approach. This is because building it on your own is not easy. If you’re interested, check out open-next.

Deploying on Vercel is convenient, but there are also some issues. Vercel’s SSR renders in a serverless manner, so its scalability is certainly excellent. However, Serverless has a Cold Start issue, which can cause the response to be considerably slow during the initial access. I have explored solutions to this problem.

TL;DR

From version 13.4 of Next.js, the app folder becomes officially available, but it seems that the Cold Start issue for serverless functions has not been resolved. In cases where caching is not possible and SSR (Server-Side Rendering) is required every time, let’s render at the Edge instead of using Node.js for rendering.

It is desirable to avoid using the old getInitialProps, as Edge rendering is not available with it.

Measurement results

Preconditions

  • Execute SSR for each request without caching
  • Minimize server-side processing time
  • Do not consider the response bundle size
  • Set Vercel’s server to Tokyo-hnd1

getInitialProps

Implementation is very easy. Add the following to _app.tsx:

pages/_app.tsx
Copy
// use getInitialProps to fetch data
MyApp.getInitialProps = async (appContext: AppContext) => {
  const appProps: any = await App.getInitialProps(appContext);
  const { req } = appContext.ctx;

  if (req) {
    const host = req.headers.host;
    const dataRes = await fetch("https://jsonplaceholder.typicode.com/todos/1");

    return { pageProps: appProps.pageProps, host, data: await dataRes.json() };
  } else {
    const { host, data } = window.__NEXT_DATA__.props;

    return { pageProps: appProps.pageProps, host, data };
  }
};

It is found that the response time from the server takes 2 seconds or more for the initial access (Cold Start).

getInitialProps-cold-start

When accessing with an already launched instance, the response time from the server is around 400ms.

getInitialProps-started

getServerSideProps(Serverless Function)

getServerSideProps can be implemented similarly to getInitialProps.

pages/index.tsx
Copy
export const getServerSideProps = async () => {
  const dataRes = await fetch("https://jsonplaceholder.typicode.com/todos/1");

  return {
    props: {
      data: await dataRes.json(),
    },
  };
};

Similar to getInitialProps, it is found that the response time from the server takes 2 seconds or more for the initial access (Cold Start).

getServerSideProps-cold-start

When accessing with an already launched instance, the response time from the server is around 400ms.

getServerSideProps-started

getServerSideProps(Edge Function)

Edge rendering is a promising candidate. With Edge Function, even without caching, the startup time is only about 80ms, and implementation is also very easy. Simply specify the rendering environment in index.tsx of getServerSideProps.

pages/index.tsx
Copy
export const config = {
  runtime: "experimental-edge",
};

It will be significantly improved. Even for the initial access, it will be around 150ms.

getServerSideProps-edge-cold-start

If you access again immediately, it will be around 50ms.

getServerSideProps-edge-started

App Router(Serverless Function)

You can add a function to retrieve data to page.tsx and use React Server Components.

app/page.tsx
Copy
async function getData() {
  const res = await fetch("https://jsonplaceholder.typicode.com/todos/1", {
    cache: "no-store",
  });
  return await res.json();
}

export default async function Home() {
  const dataPromise = getData();

  const data = await dataPromise;

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <pre>{JSON.stringify(data)}</pre>
    </Suspense>
  );
}

The initial access (Cold Start) still takes about 2 seconds. While the issue of bundling is resolved with React Server Components, the problem of Cold Start is not resolved.

app-cold-start

When accessing with an already launched instance, the response time from the server is around 400ms.

app-started

appフォルダー(Edge Function)

The implementation is almost the same as for the Serverless Function, just specify the runtime.

app/page.tsx
Copy
export const runtime = "edge";

The response time for the initial access is only around 200ms.

app-edge-cold-start

If you access again immediately, it will be around 40ms.

app-edge-started

Summary

Initial Access (Cold Start) Access again immediately
getInitialProps 2s 400ms
getServerSideProps(Serverless Function) 2s 400ms
getServerSideProps(Edge Function) 150ms 50ms
appフォルダー(Serverless Function) 2s 400ms
appフォルダー(Edge Function) 200ms 40ms

If it takes about 3 seconds for a website to load, the bounce rate may increase significantly. To reduce the bounce rate of pages where caching cannot be used, it is a good choice to use Edge rendering, which is a trend.


Relative Posts

Enable your website's multi zones with Next.js and GatsbyJS

November 27, 2022

ThunderMiracle

Blog part of ThunderMiracle.com