Next.js: Feature request - Export static app with client side getInitialProps and rendering

Created on 25 Jun 2018  Â·  12Comments  Â·  Source: vercel/next.js

Feature request

Is your feature request related to a problem? Please describe.

The current next export requires an exportPathMap that enumerates all possible pages. For sites with a large number of pages and frequently updated content (e.g. e-commerce product catalog), pre-rendering all possible pages is slow and output becomes stale very quickly.

Describe the solution you'd like

A version of next export that generates a static site that executes getInitialProps and performs all rendering client side. Maybe next export-client-render?

When using a static host with integrated build pipeline (e.g. Github, Gitlab), this feature can be extremely convenient. This is useful for sharing work in progress during development.

Describe alternatives you've considered

The most obvious alternative is to avoid using getInitialProps then you'll end up with a regular react app that does everything client side. However, I would like to have cool pre-rendering in production and fast build + cheap deployment for demo and prototyping.

Is there a way to hack getInitialProps so it doesn't run during next export? Is there some react lifecycle that would allow me to run getInitialProps client side and patch the results into props?

Limitations

  1. No way to support custom routing logic
  2. getInitialProps needs to work client side

Most helpful comment

Not sure if this is still relevant, but I was facing a similar issue and wanted getInitialProps to run client-side. I managed to solve this with a custom App Component, which keeps track if props have been resolved server-side.

import App, { Container } from "next/app";
import Router from "next/router";
import React from "react";

export default class MyApp extends App {
    static async getInitialProps({ Component, ctx }) {
        let pageProps = {};

        // tell the client if the initial props still need to be fetched
        let clientNeedsProps;

        // check if we are inside a server-side export
        const isExport = (!process.browser && !(ctx && ctx.req && ctx.req.headers));

        if (isExport) {
            // inside an export, the client needs props
            // iff .getInitialProps is defined
            clientNeedsProps = !!Component.getInitialProps;
        } else {
            // if not in an export, fetch props as normally
            if (Component.getInitialProps)
                pageProps = await Component.getInitialProps(ctx);
            // and tell the client that it got all the props it needed
            clientNeedsProps = false;
        }

        return { pageProps, clientNeedsProps };
    }

    // keep track if we got the initial props
    state = { gotInitialProps: !this.props.clientNeedsProps };

    async componentDidMount() {
        // if we still need some properties, update the router
        // which should call getInitialProps() and get them
        if (this.props.clientNeedsProps) {
            await Router.replace(Router.pathname + location.search);
            this.setState({ gotInitialProps: true });
        }
    }

    render() {
        const { Component, pageProps } = this.props;
        const { gotInitialProps } = this.state;

        // we only render the Component if we got the initial props
        return <Container>{gotInitialProps && <Component {...pageProps} />}</Container>
    }
}

All 12 comments

Next.js 6.0.0 made exportPathMap optional. Not sure if that solves all your problems...

Assuming I have 10000 products with id 1 to 10000. I have a productPage that fetches the product data in getInitialProps

With optional/auto exportPathMap

productPage will be exported but it will be useless because no product info fetched
productPage?id=10000 will not be exported. Most static file host will serve the empty productPage page

With manually generated exportPathMap

productPage?id=10000 will be exported, but the export process will take a long time because the export process needs to fetch 10000 products and render 10000 pages. Also, most static file host won't serve the correct file so you'll need to mess around with custom routing to map the above to 'productPage/1000.html'

With an export method that does client side rendering

productPage?id=10000 will work because only the base productPage is exported. Exporting is instant because no http requests or rendering takes place. All static file host will serve the static file correctly by default. There is no need for custom routing logic and exportPathMap. Custom routing won't work unless your server supports some routing logic (like .htaccess or nginx.conf)

With manually generated exportPathMap

You say you will need to map productPage/1000.html to productPage?id=1000, this is not true, when exporting you define:

"productPage/1000": { page: "productPage", query: { id: 1000 } }

And Next will export productPage/1000.html (actually productPage/1000/index.html)

With an export method that does client side rendering

I think if you want this then Next.js is not a good option, what you need is something like Create React App which will gives you a single HTML which could work for every pathname and then route and fetch data client side.

If you still want Next.js you could export a single HTML, use it to server every /productPage/:id and then in componentDidMount fetch the data, but if you are doing this then Next.js is an overkill and CRA serves you better.

@sergiodxa I fully understand where you are coming from. As mentioned in my first post, an easy work around is to just avoid using getInitialProps and use componentDidMount like you suggested. However, there's no easy way to switch back to server side pre-rendering.

My proposal is useful when

  1. You want to use server side pre-rendering in production
  2. You need to frequently share the current "work in progress" with others
  3. You have a large amount of dynamic content and it is difficult to build a exportPathMap of all your dynamic content.

I think many projects meet all 3 criteria above.

With the feature I proposed, any code you push to Github / Gitlab can be automatically built and publicly accessible with almost no effort.

If this feature doesn't make sense or if it's not interesting to most people, please feel free to close it.

Not sure if this is still relevant, but I was facing a similar issue and wanted getInitialProps to run client-side. I managed to solve this with a custom App Component, which keeps track if props have been resolved server-side.

import App, { Container } from "next/app";
import Router from "next/router";
import React from "react";

export default class MyApp extends App {
    static async getInitialProps({ Component, ctx }) {
        let pageProps = {};

        // tell the client if the initial props still need to be fetched
        let clientNeedsProps;

        // check if we are inside a server-side export
        const isExport = (!process.browser && !(ctx && ctx.req && ctx.req.headers));

        if (isExport) {
            // inside an export, the client needs props
            // iff .getInitialProps is defined
            clientNeedsProps = !!Component.getInitialProps;
        } else {
            // if not in an export, fetch props as normally
            if (Component.getInitialProps)
                pageProps = await Component.getInitialProps(ctx);
            // and tell the client that it got all the props it needed
            clientNeedsProps = false;
        }

        return { pageProps, clientNeedsProps };
    }

    // keep track if we got the initial props
    state = { gotInitialProps: !this.props.clientNeedsProps };

    async componentDidMount() {
        // if we still need some properties, update the router
        // which should call getInitialProps() and get them
        if (this.props.clientNeedsProps) {
            await Router.replace(Router.pathname + location.search);
            this.setState({ gotInitialProps: true });
        }
    }

    render() {
        const { Component, pageProps } = this.props;
        const { gotInitialProps } = this.state;

        // we only render the Component if we got the initial props
        return <Container>{gotInitialProps && <Component {...pageProps} />}</Container>
    }
}

You can use componentDidMount to only fetch on the client side.

You can use componentDidMount to only fetch on the client-side.

We are losing the benefits of static.
I would like this functionality working with a statically exported application.

especially now that we cant statically export a page that contains the word "getInitialProps"

I'm really not sure what you're referring to @david-genger

@timneutkens Disregard, I was having another issue that tirped me up.
BTW the error messages when building can sometimes be very unhelpful.

@david-genger can you elaborate so that we can improve them if possible?

When running next start, the error messages are very clear and detailed and to the point. I would like that the errors of next build should reflect the same errors that I get when I run the next start


From: Tim Neutkens notifications@github.com
Sent: Wednesday, September 4, 2019 10:55 AM
To: zeit/next.js
Cc: David; Mention
Subject: Re: [zeit/next.js] Feature request - Export static app with client side getInitialProps and rendering (#4663)

@david-gengerhttps://github.com/david-genger can you elaborate so that we can improve them if possible?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://github.com/zeit/next.js/issues/4663?email_source=notifications&email_token=AC4HWIWQPJGIPRW5X7JSVBDQH7D45A5CNFSM4FGWBGC2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD533EBA#issuecomment-527938052, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AC4HWIQFX3H2WXZXNK2L5X3QH7D45ANCNFSM4FGWBGCQ.

@david-genger that's quite vague, can you give an example of an error that you got?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pie6k picture pie6k  Â·  3Comments

flybayer picture flybayer  Â·  3Comments

formula349 picture formula349  Â·  3Comments

rauchg picture rauchg  Â·  3Comments

olifante picture olifante  Â·  3Comments