-
Notifications
You must be signed in to change notification settings - Fork 27k
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
Comments
I'm interested in seeing what circumstance we would need to return additional data other than what can be contained within |
Looks very interesting! Would his be a replacement for |
It's more so about future proofing the method so that it allows us to extend it later on if needed. |
In general that behavior has some downsides, for example waterfall fetches that could be slow from certain areas around the world. The |
This looks really interesting! Cool idea! I have concerns about deployment though... Let's imagine I'm hosting on Now. 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:
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 |
@joltmode that's basically the case for every static site generator currently. |
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? |
@kibs the return values would always relate to how the props are handled. So the naming is fine imo. |
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 Can't wait to try this out!
|
Fixed! |
This sounds great! I just wonder from a TypeScript user perspective if having 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 |
@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. |
@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) => ... |
@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. |
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. |
@timneutkens I played around with canary, trying to port some I am trying to read the .md files of my 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 My other question, more generally, is what you imagine best practices will be for using import vs. require in Index.getStaticProps = async () => {
const fs = require('fs-extra'); // only require when needed at SSG
const props = await fs.readdir('./pages/blog');
return { props: { posts } };
}; |
Ah, I think I get what you mean. Would that mean that |
@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.
|
@jaredpalmer tree shaking wasn't implemented yet on canary. |
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:
In some applications, it is either impractical or impossible to know the routes at build time. A few examples would be:
Routes for pages like this will probably be in the form 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 Again, thank you for your thoughtfulness in how you run this project |
Yeah they are as one is static generation and one is server-side rendering. |
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 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) |
@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:
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 P.S. I can create an issue for this use case if you think it's better to discuss it separately. |
@grushetsky can you create a different issue. This is a completely different question from what is being discussed in the RFC 👍 |
@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:
and in an other page I do:
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 |
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
for the Home page. For some reason NextJS fails to match
to
If I provide
For the record, I know it's experimental but this thread is to give feedback, right? |
There's a feature request open for this: #10488 |
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. 🤔 🧐 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 If that makes some sense; am happy to jump on a call and chat about this. |
How to handle
In the example above, visiting Are we supposed to have similar conditions for requests to |
For using |
@tylermcrobert how do you imagine grabbing cookies when there's no request at all?! |
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 |
Maybe I'm misunderstanding the fallback option in that case. What you're saying totally makes sense in the build-time context. Isn't |
@tylermcrobert yes 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. |
@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 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 |
Stay tuned, more soon 🕵 |
So if I understand this correct, we are able to use fallback true when for example in my case a user registers (no static page is generated since it’s a new page/user) but when the profile gets a visit it’s generated automatically?
Erhan Karadeniz
http://www.erhankaradeniz.com
… On 4 Mar 2020, at 20:25, Tim Neutkens ***@***.***> wrote:
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 🕵
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
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. |
Yeah what I meant was a placeholder page.. I’d fetch userdata on client-side indeed. |
@timneutkens Will there be any way of deleting or rebuilding specific statically generated pages? |
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 !!! |
@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 Concrete this would mean Thanks in advance and keen to hear your thoughts, especially if that's something for the future that could be solved/worked around ❤️ |
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 |
I just played with 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 |
@martpie You might want to check out this comment: #9524 (comment) |
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! |
Please post any questions to the Next.js GitHub Community! |
This is so cool! Thanks for the hard work! |
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.
getStaticProps
- Opt-in to static generation (SSG) atnext build
time.getServerSideProps
- Opt-in to server-side rendering (SSR) which renders on-demand.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 uponnext 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
withgetInitialProps
there is another problem that surfaces.getInitialProps
is called at build time (which is great), but then when you usenext/link
to move between pagesgetInitialProps
is called client-side, instead of using thenext 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
andgetServerSideProps
. But also a way to provide parameters to statically generate static pages for dynamic routes:getStaticPaths
(replacement forexportPathMap
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 runningnext build
)getStaticPaths
allows for returning a list of parameters to generate at build time for dynamic routesgetServerSideProps
marks the page to be server-side rendered on every request and is the most similar to the currentgetInitialProps
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 atnext build
time.Next.js will also automatically generate a JSON file that holds the result of
getStaticProps
atnext build
time. This is being used for client-side routing.When client-side routing through
next/link
ornext/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.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 aexportPathMap
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 agetStaticPaths
method that allows for returning a list of urls. Since it's aasync
method you can also fetch that list from a data source like your CMS.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:
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
fromgetStaticPaths
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
ornext/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 beinggetServerSideProps
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 aprops
key.The
context
will contain:params
- The parameters on a dynamic routereq
- The HTTP request objectres
- The HTTP response objectquery
- The query string (not entirely sure about this one, but probably needed)Authored by @timneutkens, @Timer, @ijjk, @lfades. Collaborated with @rauchg, @jescalan and others 🚀
The text was updated successfully, but these errors were encountered: