Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static Generation / SSG Improvements #9524

Closed
timneutkens opened this issue Nov 25, 2019 · 250 comments
Closed

Static Generation / SSG Improvements #9524

timneutkens opened this issue Nov 25, 2019 · 250 comments
Milestone

Comments

@timneutkens
Copy link
Member

timneutkens commented Nov 25, 2019

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 🚀

@timneutkens timneutkens changed the title Static Generation (SSG) Improvements Static Generation / SSG Improvements Nov 25, 2019
@maxchehab
Copy link

maxchehab commented Nov 26, 2019

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: {}
  };
}

I'm interested in seeing what circumstance we would need to return additional data other than what can be contained within props. I found the in-line explanation "to further control behavior per-page" a bit vague.

@neeharv
Copy link

neeharv commented Nov 26, 2019

Looks very interesting! Would his be a replacement for getInitialProps or along side? For eg, for our use case, the data fetching API is a public service. So on client side navigation, we expect the client to directly call the API layer, whereas on SSR the server calls it. Going forward, would this use case continue to be solved by the prior method?

@timneutkens
Copy link
Member Author

I'm interested in seeing what circumstance we would need to return additional data other than what can be contained within props. I found the in-line explanation "to further control behavior per-page" a bit vague.

It's more so about future proofing the method so that it allows us to extend it later on if needed.

@timneutkens
Copy link
Member Author

timneutkens commented Nov 26, 2019

Looks very interesting! Would his be a replacement for getInitialProps or along side? For eg, for our use case, the data fetching API is a public service. So on client side navigation, we expect the client to directly call the API layer, whereas on SSR the server calls it. Going forward, would this use case continue to be solved by the prior method?

In general that behavior has some downsides, for example waterfall fetches that could be slow from certain areas around the world. The getServerProps approach allows for caching the response more efficiently.

@tomsseisums
Copy link

tomsseisums commented Nov 26, 2019

This looks really interesting! Cool idea!

I have concerns about deployment though...

Let's imagine I'm hosting on Now.
For the first deployment, it's obvious that the whole applications gets built on deployment.

Then, I change some content in CMS and am looking to trigger rebuild of SSG pages only, but the application code has not changed.

Immediately alarm goes off, that in this case if I trigger the build, there are two possible solutions:

  1. Nothing gets rebuilt, as everything is cached - no code has changed and blabla.
  2. I --force it, and now "everything" gets rebuilt, but I only required the SSG pages to be rebuilt.

These are just hypotheses, as that depends on build systems themselves - how aware are they of Next

This would probably affect any other hosting solution.

Next itself has a .next/cache... how would this play around that?

@timneutkens
Copy link
Member Author

@joltmode that's basically the case for every static site generator currently. .next/cache is preserved between deployments on Now and reused. Keep in mind that you're currently probably using getInitialProps for this case with caching (potentially https://zeit.co/blog/serverless-pre-rendering), which dynamically renders in a serverless function and then caches on our CDN, this behavior is still totally fine and will continue to work if you use getServerProps.

@kibs
Copy link

kibs commented Nov 26, 2019

Really awesome, would fit nicely into how we are using Next for customer projects, and would remove some boilerplate code we copy around.

One thing to consider is the naming of getStaticProps and getServerProps, if they return a { props } and potential other options in the future, would the *Props not be confusing? Maybe getStaticConfiguration, getStaticSetup, getStaticOptions would be more generic?

@timneutkens
Copy link
Member Author

@kibs the return values would always relate to how the props are handled. So the naming is fine imo.

@hormesiel
Copy link

hormesiel commented Nov 26, 2019

This is simply awesome! This is solving every use case and need I've had recently or could think of while developing both private and professional web apps. You just prevented me from starting my own hybrid site generator, thanks!

I can also relate to the new methods being better than the previous getInitialProps() and exportPathMap(), which sounded a bit confusing to me at first when I started using Next.js and digged into SSR / SSG. The per-page approach makes more sense to me too.

Can't wait to try this out!

Just a side note : in the last example I think getServerProps() is missing a context param.

@timneutkens
Copy link
Member Author

Just a side note : in the last example I think getServerProps() is missing a context param.

Fixed!

@jstcki
Copy link
Contributor

jstcki commented Nov 26, 2019

This sounds great! I just wonder from a TypeScript user perspective if having getStaticProps, getStaticPaths and getServerProps as static methods on the page component (like getInitialProps at the moment) would be easier to type/use correctly.

const Page: NextPage<Props> = (props) => ...

// Explicit types needed here
export const getStaticPaths: NextGetStaticPaths<Params> = () => ...
export const getStaticProps: NextGetStaticProps<Props, Params> = (context) => ...
export const getServerProps: NextGetServerProps<Props> = (context) => ...

export default Page

// vs.

const Page: NextPage<Props, Params> = (props) => ...

// Static method types come from NextPage<Props, Params>
Page.getStaticPaths = () => ...
Page.getStaticProps = (context) => ...
Page.getServerProps = (context) => ..

export default Page

@timneutkens
Copy link
Member Author

@herrstucki the problem with that approach is that it becomes significantly harder to tree shake (read: close to impossible). Which would mean unnecessary code would be shipped to the browser.

@jstcki
Copy link
Contributor

jstcki commented Nov 26, 2019

@timneutkens good point … but would then a separate file not make even more sense? Or is something like this reliably tree-shakable?

// This should all be removed in client-side code …
import {fetchQuery, queryTag} from 'big-data-fetching-lib';
const query = queryTag`...`
export const getStaticProps = async () => ({ props: await fetchQuery(query) })

// Only this should be included client-side
export default (props) => ...

@timneutkens
Copy link
Member Author

@herrstucki we can reliably tree shake that, but we're also still discussing having a separate file. Personally I prefer the single file approach though.

@neeharv
Copy link

neeharv commented Nov 26, 2019

Looks very interesting! Would his be a replacement for getInitialProps or along side? For eg, for our use case, the data fetching API is a public service. So on client side navigation, we expect the client to directly call the API layer, whereas on SSR the server calls it. Going forward, would this use case continue to be solved by the prior method?

In general that behavior has some downsides, for example waterfall fetches that could be slow from certain areas around the world. The getServerProps approach allows for caching the response more efficiently.

Sure, but I'm talking about avoiding the RTT to the react server at all. Consider the case where the SSR output from the server is cached at CDN / cache server proxy. Couple that with data fetching for client navigation directly calling a different API layer (common to web / apps / all clients), means that the Next.js server layer doesn't have to be scaled up as much in a high traffic scenario.

I understand your point of waterfall fetches, but giving consumers the ability to treat the Next server as a SSR layer vs a data source would allow for much better scaling setups.

@timneutkens
Copy link
Member Author

Looks very interesting! Would his be a replacement for getInitialProps or along side? For eg, for our use case, the data fetching API is a public service. So on client side navigation, we expect the client to directly call the API layer, whereas on SSR the server calls it. Going forward, would this use case continue to be solved by the prior method?

In general that behavior has some downsides, for example waterfall fetches that could be slow from certain areas around the world. The getServerProps approach allows for caching the response more efficiently.

Sure, but I'm talking about avoiding the RTT to the react server at all. Consider the case where the SSR output from the server is cached at CDN / cache server proxy. Couple that with data fetching for client navigation directly calling a different API layer (common to web / apps / all clients), means that the Next.js server layer doesn't have to be scaled up as much in a high traffic scenario.

I understand your point of waterfall fetches, but giving consumers the ability to treat the Next server as a SSR layer vs a data source would allow for much better scaling setups.

I think you're misunderstanding that this new behavior means you can actually cache the full results on a CDN given the CDN supports dynamic responses. This was previously not reliably possible with getInitialProps.

@jaredpalmer
Copy link
Contributor

jaredpalmer commented Nov 26, 2019

@timneutkens I played around with canary, trying to port some babel-plugin-preval code to getStaticProps. I am ran into an issue with fs.

I am trying to read the .md files of my ./pages/blog/ directory and loop through them so I can make a blog index page with all my posts

import React from 'react';
import Link from 'next/link';
import fs from 'fs-extra';

const Index = ({ posts }) => (
  <div>
    Hello World. <Thing msg="hello" />
    <Link href="/thing">
      <a>About</a>
    </Link>
    {posts.map(p => (
      <div key={p.title}>{p.title}</div>
    ))}
  </div>
);

Index.getStaticProps = async () => {
  const items = await fs.readdir('./pages/blog');
  items.forEach(path => /* .... do some stuff ... */ )
  return { props: { posts: items } };
};

export default Index;

This code leads to this error:

Module not found: Can't resolve 'fs' in '/Users/jared/Downloads/nextjs-typescript-template/node_modules/fs-extra/lib'

IIRC from Razzle, this error has to do with webpack's filesystem stubs (or lack thereof). I think I once fixed this with Razzle by adding this to the webpack config.

node: {
  fs: "empty";
}

I tried this next.config.js, but it just makes the error go away. It appears though that fs/fs-extra doesn't actually work, or it does and perhaps paths don't (unclear to me). Any thoughts on that?

My other question, more generally, is what you imagine best practices will be for using import vs. require in getStaticProps. If I'm not mistaken, my above snippet will attempt to import fs-extra in React isomorphically??. Would it thus be better to change the import to an inline require like this?

Index.getStaticProps = async () => {
  const fs = require('fs-extra');  // only require when needed at SSG
  const props = await fs.readdir('./pages/blog');
  return { props: { posts } };
};

@neeharv
Copy link

neeharv commented Nov 26, 2019

I think you're misunderstanding that this new behavior means you can actually cache the full results on a CDN given the CDN supports dynamic responses. This was previously not reliably possible with getInitialProps.

Ah, I think I get what you mean. Would that mean that getServerProps on the first SSR generation would create a unique endpoint, in a content addressable hash maybe in the URL maybe that we can then cache on the CDN? The only downside of this would be that said cache would not be shareable between non Next apps (android / ios) and Next apps. Additionally, with an external data source, the cache control directives are upstream, but here since Next would assume responsibility of serving up the data, we need APIs or props to specify those for the generated data endpoints.

@jstcki
Copy link
Contributor

jstcki commented Nov 26, 2019

@jaredpalmer I assume #9524 (comment) (including my concern about reliable tree-shakeability) would be resolved by having a separate file that would be compiled completely separately from client bundle code? E.g.

pages/
    foo.js
    foo.data.js (<- exports getStaticProps etc.)

or:

pages/
    foo.js
pages-data/
    foo.js (<- exports getStaticProps etc.)

@timneutkens
Copy link
Member Author

@jaredpalmer tree shaking wasn't implemented yet on canary.

@baer
Copy link

baer commented Nov 26, 2019

As always, thank you for everything y'all do. Next.js has been an absolute joy to work with, and as I've said before, just about every feature release lets us reduce the size of the codebases I manage. It's amazing.

It's hard to be critical of this RFC since, as written, it is immediately useful for a LOT of applications. I do, however, want to address one line that I'm not sure I agree with:

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

In some applications, it is either impractical or impossible to know the routes at build time. A few examples would be:

  • User profile pages
  • Product pages (for companies with a fast-changing inventory)
  • Sales Order details pages

Routes for pages like this will probably be in the form /entity-name/entity-id and Next's dynamic routes work really really well since you can do things like router.push('/customers/[customerId]', '/customers/baer'). There is still a catch. If you plan to serve these files statically with something like Serve, Netlify, NGINX, etc, you'll need to generate a set of redirects so that users won't get a 404 on page-refresh and, to do that, you still need exportPathMap.

The following is copied, almost as-is, from a codebase I work on regularly:

const buildServeConfig = redirects => {
  const config = {
    public: `dist`,
    trailingSlash: true,
    rewrites: redirects
  };

  const outputPath = `${__dirname}/serve.json`;

  fs.writeFile(outputPath, JSON.stringify(config, null, 2), err => {
    if (err) {
      throw err;
    }
    // eslint-disable-next-line no-console
    console.log(`Generated: ${outputPath}`);
  });
};

...

exportPathMap: function(defaultPathMap, { dev, outDir }) {
  const redirects = Object.entries(defaultPathMap)
    // No need to create a redirect rule for `/dirname/` or `/dirname/index.html`
    .filter(([url]) => url !== `/` && url !== `/index`)
    .map(([url, { page }]) => ({
      // Replaces /[customerId] with /:customerId
      source: url.replace(/]/g, ``).replace(/\[/g, `:`),
      destination: `${page}/index.html`
    }));

  // By default, the routes are sorted such that a route like `/order/:orderId`
  // comes before `/order/new`. Since the `:orderId` portion of `/order/:orderId` 
  // is a wildcard, the route `/order/new` will be a match and consider `new` 
  // as a value for `:orderId`. To get past this, we sort the redirects by the 
  // number of parameters in ascending order.
  const sortedRedirects = [...redirects].sort(
    (currentRedirect, nextRedirect) =>
      currentRedirect.source.split(`:`).length >
      nextRedirect.source.split(`:`).length
  );

  buildServeConfig(sortedRedirects);

  return defaultPathMap;
}

I understand that this RFC does not deprecate or remove any APIs and I also recognize that it is possible to build these redirects by traversing the build directory so even if it were deprecated, I've got a nice ecape hatch. But, it doesn't quite "remove the need for getStaticPaths."

Again, thank you for your thoughtfulness in how you run this project

@timneutkens
Copy link
Member Author

Are getStaticProps/getStaticPaths and getServerProps mutually exclusive? i.e. would it be possible to have a part prerendered and a part dynamic at the same time?

Yeah they are as one is static generation and one is server-side rendering.

@jkjustjoshing
Copy link

This fixes one of the big things I miss from Gatsby before we migrated to Next:

We have a monolithic (100s of kbs) JSON file that we pull data from to render our pages that never changes. In Gatsby we loaded the JSON file into the GraphQL schema and queried against that, only grabbing the data that we needed to render a given page. With Next, the easiest/cleanest way we found to do this is import monolith from './monolith.json', which requires the user download the entire JSON file.

This RFC 100% addresses this use case and brings Next one step closer to being on-par with Gatsby in the areas Gatsby shines (obviously, Gatsby can't do runtime SSR, so I'm only talking about static buildtime renders)

@grushetsky
Copy link
Contributor

grushetsky commented Nov 28, 2019

@timneutkens, thank you for the RFC!

I have a use case for Next.js that I recently discussed with @rauchg.

Next.js delivers very smooth DX and some reasonable defaults. So, I'm interested in using Next.js for a client-side only rendered application, a Smart TV app.

Smart TV apps are almost classic web apps that are run by TV’s browser engine:

  1. The app is packaged into a bundle: styles, scripts, images, index.html, certificates and TV config file.
  2. The bundle is submitted for review to platform’s app store.
  3. The bundle is then installed from the store as an app and is run by the user.

The thing is that the bundle is statically hosted by the TV device itself and not loaded from the server. Thus, no SSR option is possible (Node.js isn’t exposed to developers for these purposes). But the app itself is dynamic (say, Netflix).

So, we need to run an SPA that is hosted by a static web server.



As I understand opting-out of getServerProps (or getInitialProps) completely will help to avoid SSR. But what happens with dynamic rendering on the client? And what about routing in this case? According to this RFC the problem hasn’t been addressed yet. @timneutkens, could you, please, suggest the best way to enable client-side only rendering in Next.js? And whether it fits Next.js in the first place? Thanks!

P.S. I can create an issue for this use case if you think it's better to discuss it separately.

@timneutkens
Copy link
Member Author

@grushetsky can you create a different issue. This is a completely different question from what is being discussed in the RFC 👍

@erhankaradeniz
Copy link

@Timer for now I have reverted back to [email protected] where I still can use params, until I figure out why it's not working with paths.

this is how I currently generated my paths:

return cityData.map(city => {
    return {
      params: {
        country: city.countrySlug,
        city: city.slug,
      },
    }
  })

and in an other page I do:

return cityData.map(city => {
    return {
      params: {
        country: city.countrySlug,
        city: city.slug,
      },
    }
  })

haven't managed to convert it to the new canary release with the paths. I must be doing something wrong, because console.logs are not even triggered within getStaticPath

@ivan-kleshnin
Copy link
Contributor

ivan-kleshnin commented Mar 2, 2020

I have troubles with nested path prerendering and SSG:

// pages/[lang]/[...slugs].js

export async function getStaticPaths() {
  let knex = await import("knex/client").then(m => m.default)
  let pages = await knex("page").select(["lang", "url"])
  return {
    fallback: true,
    paths: pages.map(page => {
      return {
        params: {
          lang: page.lang,
          slugs: page.url == "/" ? [] : page.url.slice(1).split("/"),
        }
      }
    }),
  }
}

leads to

Error occurred prerendering page "/en/". Read more: https://err.sh/next.js/prerender-error:
Error: The provided export path '/en/' doesn't match the '/[lang]/[...slugs]' page.

for the Home page. For some reason NextJS fails to match

{lang: "en", slugs: []}

to

/[lang]/[...slugs]

If I provide {lang: "en", slugs: ["/"]} it builds but with a wrong URL:

├ ● /[lang]/[...slugs]      875 B        204 kB
├   ├ /en/credits
├   ├ /en/%2F

For the record, getServerSideProps works fine with a similar setup.

I know it's experimental but this thread is to give feedback, right?

@timneutkens
Copy link
Member Author

pages/[lang]/[...slugs].js matches /en/abcdef and not /en, for that you currently have to create pages/[lang]/index.js.

There's a feature request open for this: #10488

@ahmadawais
Copy link
Contributor

First of all, this is awesome. I've been hoping to have something like this in Next.js so I could finally move away from Gatsby.js and have a hybrid app (static + dynamic).

🚀 I tried the canary and half baked complex app version worked fine. I confess I haven't read all the comments here but not sure if the tree-shaking is implemented yet.

🤔 getStaticPaths feels a lot more like setStaticPaths where we are defining the static path for the SSG behavior. That kind of confused me a little.

🧐 I wonder if we can improve the build times by having build categories? I know this would complicate the setup but it will be well worth it. Let me explain:

What if we have something like setBuildCategory that sets it to blog or pages or whatever someone wants 2020-content. Then the SSG builder looks for the category of the page that was changed and only tries to rebuild that category from a combination of cache + new render. Something like this can help us make SSG fast and avoid huge build times for things that are not prone to change a lot but could still change so can't be archived.

If that makes some sense; am happy to jump on a call and chat about this.

@oMatej
Copy link

oMatej commented Mar 3, 2020

How to handle getServerSideProps with custom server implementation?

if (pathname === '/a') {
  app.render(req, res, '/b', query)
}

In the example above, visiting /a will render the page pages/b.js. But a client-side redirection to /a attempts to download a.json file, that does not exist in this case.

Are we supposed to have similar conditions for requests to /_next/data/{BUILD_ID}/{PAGE}.json to render different JSON files?

@tylermcrobert
Copy link

For using fallback: true in getStaticPaths, how do I get the req object? It appears currently that I cannot. The reason I need it is to grab some cookies from the browser to authenticate a route

@ivan-kleshnin
Copy link
Contributor

ivan-kleshnin commented Mar 4, 2020

@tylermcrobert how do you imagine grabbing cookies when there's no request at all?!
Routes with backend depending on real visitor requests can't be made static by definitions of "static" and "dynamic". Not to say you can't combine static and auth... it's just auth part will belong to API and client code instead of pages.

@timneutkens
Copy link
Member Author

How to handle getServerSideProps with custom server implementation?

if (pathname === '/a') {
  app.render(req, res, '/b', query)
}

In the example above, visiting /a will render the page pages/b.js. But a client-side redirection to /a attempts to download a.json file, that does not exist in this case.

Are we supposed to have similar conditions for requests to /_next/data/{BUILD_ID}/{PAGE}.json to render different JSON files?

Next.js supports dynamic route parameters, so remapping in a custom server is rarely needed anymore: https://nextjs.org/docs/routing/dynamic-routes

The approach you outlined already doesn't work with <Link> (would cause a full page transition) so getServerSideProps works already.

@tylermcrobert
Copy link

@tylermcrobert how do you imagine grabbing cookies when there's no request at all?!
Routes with backend depending on real visitor requests can't be made static by definitions of "static" and "dynamic". Not to say you can't combine static and auth... it's just auth part will belong to API and client code instead of pages.

Maybe I'm misunderstanding the fallback option in that case. What you're saying totally makes sense in the build-time context.

Isn't fallback: true for when there isn't a predefined route? In that case, a fallback would be reached from the browser, no?

@ivan-kleshnin
Copy link
Contributor

ivan-kleshnin commented Mar 4, 2020

@tylermcrobert yes fallback: true case has a request but the API has to be unified by the "lowest common denominator". I can't imagine a working system where everything is built with one set of premises and then it's incrementally updated with a totally different set of premises. It will be a disaster to support.

I think you miss the point that those incremental builds will still be cached between builds. So the role of the first visitor will influence the build result for all the consequent users! Sounds like a bad idea.

@tylermcrobert
Copy link

tylermcrobert commented Mar 4, 2020

@ivan-kleshnin I understand & certainly agree. The reason I ask is because of my specific use case.

I am using a headless CMS that allows previewing functionality so the pages that will need to be preview will not be included at build time (Because the entry being previewed will not have existed at this point). I figured this was a case where the fallback option would come in.

To access that preview, I need access to the api preview ref which is given via cookie.

Is this a case where I should just scrap useStaticProps entirely? I would hate to lose the benefit of static builds because I cannot preview my documents.

The attractiveness of this RFC over something like gatsby is it gives us “hybrid control” with static site generation that makes it less of a pain to work with headless CMSs

@timneutkens
Copy link
Member Author

I am using a headless CMS that allows previewing functionality so the pages that will need to be preview will not be included at build time (Because the entry being previewed will not have existed at this point). I figured this was a case where the fallback option would come in.

Stay tuned, more soon 🕵

@erhankaradeniz
Copy link

erhankaradeniz commented Mar 5, 2020 via email

@timneutkens
Copy link
Member Author

User data is a bad example as you'd want to fetch that client-side. Fallback is there for on-demand statically generating pages that haven't been generated at build time. Eg you might want to generate the top 100 blogposts at build time and not generate others with less traffic.

Docs will be landed for it soon.

@erhankaradeniz
Copy link

Yeah what I meant was a placeholder page.. I’d fetch userdata on client-side indeed.

@pederklev
Copy link

@timneutkens Will there be any way of deleting or rebuilding specific statically generated pages?

@BootprojectStore
Copy link

BootprojectStore commented Mar 5, 2020

Hi!!! Sound like the optimal application. I do love both React and Next!!! Made everything so elegant and simple for us to use!! But the example includes the notion blog. I would like to see an example of a an implementation when querying an Headless CMS and make the fetch done per page/post as an export as a static item.

// cheers, since it's near friday !!!

@dennisroethig
Copy link

@timneutkens this is exciting 👌

One scenario we frequently hit and I don't have a perfect solution yet with next.js or gatsby except dynamic routes or generating projects in a loop:

For historical reasons we have to deal with multiple domains (and there's no appetite to change this) that serve the same/exact pages with the exception for pricing, currency, support phone numbers and language selectors. By nature most of those marketing pages are pretty static and it would be sufficient to build them daily or weekly (vs having them to render on every request).

My question/thought: do you see a way (in the future?) the getStaticPaths could generate pages based on something that is not a route param but could be used on a request level to switch between them (eg serverless function returns static, pre-built result based on locale)

Concrete this would mean https://mysite.com/my-product and https://mysite.co.uk/my-product would serve two different static pages but without us having to generate our next app 50x times or having to hit a CMS on every request😅

Thanks in advance and keen to hear your thoughts, especially if that's something for the future that could be solved/worked around ❤️

@fabb
Copy link
Contributor

fabb commented Mar 8, 2020

I‘m thinking about a use case where I want to use SSG for high-traffic landing pages for SEO and for reducing the server load, but still want current data to be used after hydration, and on client side routing to this page. Would that be possible?

So basically on client side routing to this page, the behavior should be like getInitialProps (current data is fetched before the page becomes visible). And on server side routing to this page, the static html should be served and hydrated, and then (optionally) some api responses fetched to update some data on the page.

@martpie
Copy link
Contributor

martpie commented Mar 9, 2020

I just played with unstable_getStaticProps just to try it out and I ran into a fun conflict: it is hard to use API routes with getStaticProps.

Don't pay attention about the semantic of the code, but just the data fetching flow:

// pages/api/healthcheck.ts
import { NextApiResponse, NextApiRequest } from 'next';

export type ApiHealthCheckResponse = {
  message: 'ok';
};

const healthCheckHandler = (
  req: NextApiRequest,
  res: NextApiResponse<ApiHealthCheckResponse | ''>,
) => {
  if (req.method === 'GET') {
    return res.status(200).json({ message: 'ok' });
  }

  return res.status(405).send('');
};

export default healthCheckHandler;
// pages/index.js
// ...

export async function unstable_getStaticProps() {
  return {
    props: {
      healthcheck: (await fetch('localhost:3000/api/healthcheck').json())
    },
  };
}

The page build will crash at build time because the server is not running. I am not sure if this is a valid usecase, as getStaticProps should not be used with anything too dynamic, but I thought it was an interesting edgecase to share (I can totally imagine an Route API endpoint in charge of getting data from another API and re-format it.

@Janpot
Copy link
Contributor

Janpot commented Mar 9, 2020

@martpie You might want to check out this comment: #9524 (comment)

@Timer Timer modified the milestones: ssg, 9.3.0 Mar 9, 2020
@Timer
Copy link
Member

Timer commented Mar 9, 2020

Next-gen Static Site Generation (SSG) support has been released as stable in Next.js 9.3!

This release also includes support for "Preview Mode", or the ability to bypass the statically prerendered page and render the page on-demand for authorized users.

You can read more about it in our blog post. If you're more hands on, jump right into our docs!

@Timer
Copy link
Member

Timer commented Mar 9, 2020

Please post any questions to the Next.js GitHub Community!

@Corjen
Copy link

Corjen commented Mar 10, 2020

This is so cool! Thanks for the hard work!

@samao

This comment has been minimized.

@vercel vercel locked as resolved and limited conversation to collaborators Mar 13, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests