Skip to content

Static Generation / SSG Improvements #9524

@timneutkens

Description

@timneutkens

Summary

Allow Next.js to become fully hybrid by providing methods to do both static generation and server-side rendering on a per-page basis.

  • Two new per-page data fetching methods
    • getStaticProps - Opt-in to static generation (SSG) at next build time.
    • getServerSideProps - Opt-in to server-side rendering (SSR) which renders on-demand.
  • A new method for statically generating (SSG) a set of routes from dynamic sources
    • getStaticPaths - Return list of parameters for dynamic routes to do static generation (SSG)

This RFC exclusively discusses API additions. All new functionality is completely backwards compatible and can be incrementally adopted. This RFC introduces no deprecations.

Background

When building websites or web applications you generally have to choose between 2 strategies: Static generation (SSG) or server-side rendering (SSR).

Next.js instead lets you build hybrid applications that allow you to choose per-page which strategy is used. Starting with Next.js 9, pages without getInitialProps get statically optimized and output as .html files upon next build.

However, you might want to do data fetching while generating static pages for your specific use case.

For example, to statically generate marketing pages from a CMS or a blog section of the site.

Using getInitialProps would opt you into SSR in that case.

Next.js currently has a next export command, that makes the application fully SSG, losing the hybrid nature of Next.js.

If you use next export with getInitialProps there is another problem that surfaces. getInitialProps is called at build time (which is great), but then when you use next/link to move between pages getInitialProps is called client-side, instead of using the next export result.

This also means the data source (CMS / API endpoint) is called directly on client-side transitions, if your data source is down, client-side transitions break while moving between pages.

We've collaborated with heavy users of SSG and next export in Next.js like HashiCorp (thanks @jescalan) and extensively investigated the right constraints for introducing two new data fetching methods: getStaticProps and getServerSideProps. But also a way to provide parameters to statically generate static pages for dynamic routes: getStaticPaths (replacement for exportPathMap that is per-page).

These new methods have many advantages over the getInitialProps model as there is a clear distinction between what will become SSG vs SSR.

  • getStaticProps marks the page to be statically generated at build time (when running next build)
  • getStaticPaths allows for returning a list of parameters to generate at build time for dynamic routes
  • getServerSideProps marks the page to be server-side rendered on every request and is the most similar to the current getInitialProps behavior when using a server.

Separating these methods also allows us to provide the correct context object that can be typed using TypeScript. When you opt for a specific rendering strategy you get the correct values, currently with getInitialProps you have to guess what is available on SSG vs SSR when using TypeScript.

Furthermore, by making these methods explicit, it'll allow us to document the different trade-offs more clearly.

Implementation

Note that all these methods are top-level on the page component file and can't be nested, similar to getInitialProps.

getStaticProps

Using getStaticProps means the page will be rendered statically at build time (SSG).

This new method will allow you to do data fetching for a page that will be statically generated into a .html file at next build time.

Next.js will also automatically generate a JSON file that holds the result of getStaticProps at next build time. This is being used for client-side routing.

When client-side routing through next/link or next/router, Next.js will fetch this JSON file to get the props needed to render the page client-side.

Properties are returned under a props key so that other options can be introduced in the future.

// pages/index.js

// getStaticProps is only called server-side
// In theory you could do direct database queries
export async function getStaticProps(context) {
  return {
    // Unlike `getInitialProps` the props are returned under a props key
    // The reasoning behind this is that there's potentially more options
    // that will be introduced in the future.
    // For example to allow you to further control behavior per-page.
    props: {}
  };
}

The context will contain:

  • params - The parameters when on a dynamic route.

getStaticPaths

This is an extension on getStaticProps usage for dynamic routes.

getStaticPaths replaces the need for having a exportPathMap and works per-page.

Since you might want to statically generate a list of urls that have a dynamic parameter, like in the example below a slug. Next.js will provide a getStaticPaths method that allows for returning a list of urls. Since it's a async method you can also fetch that list from a data source like your CMS.

// pages/blog/[slug].js

// `getStaticProps` gets a `params` object holding the dynamic parameters
// For `/blog/hello-world` it would look like `{ slug: 'hello-world }`
export async function getStaticProps({ params }) {
  return {
    props: {}
  };
}

// `getStaticPaths` allows the user to return a list of parameters to
// render to HTML at build time.
export async function getStaticPaths() {
  return {
    paths: [
      // This renders /blog/hello-world to HTML at build time
      { params: { slug: "hello-world" } }
    ]
  };
}

Fallback

In many cases you might not want to pre-render every possible route in your application at build-time (for example if you have millions of products). For this reason Next.js will automatically generate a fallback page that is a render of the page without data (so that a loading state can be shown) for when the page hasn’t been generated yet.

The exact behavior of serving will be:

  • Incoming request
    • Next.js checks if the path was generated at build time
      • If the path was generated
        • serve directly
      • If the path was not generated
        • Serve the fallback
          • Next.js renders the page (with data) in the background and adds it to the list of generated pages
          • Subsequent request to the same path will serve the generated page
          • This ensures that users always have a fast experience and never have slow TTFB from server-rendering while preserving fast builds and static-generation properties

In case you want paths that weren’t generated at build time to result in a 404 that is also possible by returning fallback: false from getStaticPaths

// `getStaticPaths` allows the user to return a list of parameters to
// render to HTML at build time.
export async function getStaticPaths() {
  return {
    // Opt-out of the described fallback behavior
    fallback: false,
    paths: [
      // This renders /blog/hello-world to HTML at build time
      { params: { slug: "hello-world" } }
    ]
  };
}

getServerSideProps

When using getServerSideProps, the page is not statically generated (SSG) and instead renders on-demand on every request to the server (SSR).

Next.js will also automatically expose an API endpoint that returns the result of calling getServerSideProps. This is being used for client-side routing.

When client-side routing through next/link or next/router, Next.js will fetch this exposed API endpoint to get the JSON data that is turned into the props needed to render the page client-side.

This method is the most similar to the current getInitialProps, with the main difference being getServerSideProps is always executed server-side instead of in the browser. Either on server-side rendering or the API fetch when client-side routing.

Similarly to getStaticProps the properties are returned under a props key.

// pages/index.js

// getServerSideProps is only called server-side
// In theory you could do direct database queries
export async function getServerSideProps(context) {
  return {
    // Unlike `getInitialProps` the props are returned under a props key
    // The reasoning behind this is that there's potentially more options
    // that will be introduced in the future.
    // For example to allow you to further control behavior per-page.
    props: {}
  };
}

The context will contain:

  • params - The parameters on a dynamic route
  • req - The HTTP request object
  • res - The HTTP response object
  • query - The query string (not entirely sure about this one, but probably needed)

Authored by @timneutkens, @Timer, @ijjk, @lfades. Collaborated with @rauchg, @jescalan and others 🚀

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions