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

Allow disabling runtime JS in production for certain pages (experimental) #11949

Merged
merged 1 commit into from
Apr 17, 2020

Conversation

timneutkens
Copy link
Member

@timneutkens timneutkens commented Apr 16, 2020

This allows a page to be fully static (no runtime JavaScript) on a per-page basis.

The initial implementation does not disable JS in development mode as we need to figure out a way to inject CSS from CSS imports / CSS modules without executing the component JS. This restriction is somewhat similar to https://www.gatsbyjs.org/packages/gatsby-plugin-no-javascript/. All things considered that plugin only has a usage of 600 downloads per week though, hence why I've made this option unstable/experimental initially as I'd like to see adoption patterns for it first.

Having a built-in way to do this makes sense however as the people that do want to adopt this pattern are overriding Next.js internals currently and that'll break between versions.

Related issue: #5054 - Not adding fixes right now as this implementation needs more work. If anyone wants to work on this feel free to reach out on https://twitter.com/timneutkens

Do note that in this implementation it means no runtime JavaScript will be executed so you won't be able to use React hooks, event listeners etc without injecting custom <script> tags into the rendered html (eg through next/head) for pages that use unstable_runtimeJS: false

This allows a page to be fully static (no runtime JavaScript) on a per-page basis.

The initial implementation does not disable JS in development mode as we need to figure out a way to inject CSS from CSS imports / CSS modules without executing the component JS. This restriction is somewhat similar to https://www.gatsbyjs.org/packages/gatsby-plugin-no-javascript/. All things considered that plugin only has a usage of 600 downloads per week though, hence why I've made this option unstable/experimental initially as I'd like to see adoption patterns for it first.

Having a built-in way to do this makes sense however as the people that do want to adopt this pattern are overriding Next.js internals currently and that'll break between versions.

Related issue: vercel#5054 - Not adding `fixes` right now as this implementation needs more work. If anyone wants to work on this feel free to reach out on https://twitter.com/timneutkens
@timneutkens
Copy link
Member Author

Landing this as experimental, I can't spend tons of time on adding all the needed changes to make it work in development right now so the DX won't be what you'd expect. e.g. if you use useEffect or useState they'll work in development but won't work in production as the JS bundles will be excluded.

If someone wants to work on this feature (with guidance from me) please let me know: https://twitter.com/timneutkens, dm's are open.

@timneutkens timneutkens merged commit 2fa26a2 into vercel:canary Apr 17, 2020
@timneutkens timneutkens deleted the add/disable-runtimejs branch April 17, 2020 09:22
@timneutkens timneutkens changed the title Allow disabling runtime JS in production for certain pages Allow disabling runtime JS in production for certain pages (experimental) Apr 17, 2020
@TxHawks
Copy link
Contributor

TxHawks commented Apr 25, 2020

How do you opt-in a page to use this in latest canary? Does one also additionally need to configure something in next.config.js?

@timneutkens
Copy link
Member Author

export const config = {
  unstable_runtimeJS: false
}

export default () => <h1>My page</h1>

@Manikanta-20
Copy link

This configuration at which place, we need to add in next.config.js / certain page ?
unstable_runtimeJS: false

@timneutkens
Copy link
Member Author

It's a per-page configuration option.

@Manikanta-20
Copy link

ok,this configuration will work on next build /not?

@TxHawks
Copy link
Contributor

TxHawks commented Apr 29, 2020

Looks like the __NEXT_DATA__ script tag is still rendered to the markup.

This can be a fairly large chunk of html, and I can't think of a good reason to include it in a completely static page.

Am I missing something?

@timneutkens
Copy link
Member Author

It's there mostly because users might need certain properties from getStaticProps/getServerSideProps which are passed through there.

@TxHawks
Copy link
Contributor

TxHawks commented Apr 29, 2020

I feel that defeats the purpose of trying to create very lean pages. If the user needs anything specific from getStaticProps and have actively chosen to render pages without Next's runtime JS they can probably be expected to render a script tag to the markup with the data they need. It's nothing very complicated.

It should at least be configurable, since there is not easy way to get rid of that fairly large chunk of markup that will most likely be unneeded in most unstable_runtimeJS: false use cases.

@Manikanta-20
Copy link

this configuration

unstable_runtimeJS: false

should work for Static/Server/SSG page,not for specific server page.
At the time of next build it should not optimize/consider the page. other pages it should not break.

@TxHawks
Copy link
Contributor

TxHawks commented Apr 29, 2020

@sai1139 I'm not sure what you mean exactly - if you want to opt-out a page from including Next.js runtime JS , you configure that page to exclude it:

// in pages/index.js
export const config = {
  unstable_runtimeJS: false
}

export default () => <h1>My page</h1>

And that specific page's markup will not include the <NextScript /> generated scripts or preload links.

Making this a per-page config It actually ensures that it doesn't break other pages

@timneutkens
Copy link
Member Author

I feel that defeats the purpose of trying to create very lean pages. If the user needs anything specific from getStaticProps and have actively chosen to render pages without Next's runtime JS they can probably be expected to render a script tag to the markup with the data they need. It's nothing very complicated.

It should at least be configurable, since there is not easy way to get rid of that fairly large chunk of markup that will most likely be unneeded in most unstable_runtimeJS: false use cases.

Feel free to open a PR to change it, as said this was just an initial implementation.

@timneutkens
Copy link
Member Author

this configuration

unstable_runtimeJS: false

should work for Static/Server/SSG page,not for specific server page.
At the time of next build it should not optimize/consider the page. other pages it should not break.

Not sure what you mean. You can read the details of this PR here: #11949 (comment)

@TxHawks
Copy link
Contributor

TxHawks commented Apr 29, 2020

Happy to take a stab at this. Would you prefer I just remove it or make it configurable?

If configurable, does unstable_nextData: false work?

@timneutkens
Copy link
Member Author

Happy to take a stab at this. Would you prefer I just remove it or make it configurable?

If configurable, does unstable_nextData: false work?

Removing it when unstable_runtimeJS: false would be fine like you said.

@Manikanta-20
Copy link

@timneutkens

export const config = {
  unstable_runtimeJS: false
}

export default () => <h1>My page</h1>

this configuration is not working in developement and production

@TxHawks
Copy link
Contributor

TxHawks commented Apr 29, 2020

@sai1139 As mentioned in the description - this only work for statically generated pages, and only in production mode. It doesn't work in dev mode so that hot module reload still works

@Manikanta-20
Copy link

Manikanta-20 commented Apr 29, 2020

@TxHawks thanks for information, but i have done sample example and tested in production mode. this configuration is working you, can you share me in git url, i will get to know.

@TxHawks
Copy link
Contributor

TxHawks commented Apr 29, 2020

And your pages are statically generated?

I works fine for me. A simple reproduction repository could help figure out what the issue is

@Manikanta-20
Copy link

@TxHawks can you share me in git url, i will get to know.

@timneutkens
Copy link
Member Author

And your pages are statically generated?

I works fine for me. A simple reproduction repository could help figure out what the issue is

Do note that it's not required to statically generate the page, if you use SSR it works too 🙏

It works fine on my personal website btw: https://timn.tech/

https://github.com/timneutkens/timn.tech/blob/master/pages/index.tsx#L4-L6

@Manikanta-20
Copy link

@timneutkens thanks for the information.if other information, i will let you know.

@chrisabrams
Copy link

Is export const config a next.js convention for the pages?

@timneutkens
Copy link
Member Author

Is export const config a next.js convention for the pages?

Yes, it's currently only used for a few options though. E.g. to enable AMP support or to disable body parsing on API routes.

@chrisabrams
Copy link

I see; this is the first feature for Next where I've seen this pop up; the timing of it is incredible as I was needing this!

@TxHawks
Copy link
Contributor

TxHawks commented Apr 29, 2020

@timneutkens How would I go about testing that my changes actually work? contributing.md only mentions running the integration apps in dev mode, which obviously not help here, because these are production mode-only changes.

It actually required a very small change to _document.tx

- 692          {staticMarkup ? null : (
+ 692          {(staticMarkup || disableRuntimeJS) ? null : (
  693            <script
  694              id="__NEXT_DATA__"
  695              type="application/json"

@chrisabrams
Copy link

@TxHawks what about using puppeteer and disabling JS?

@timneutkens
Copy link
Member Author

We have production tests too, I didn’t add tests for this behavior yet as it was just a proof of concept. You can copy tests/integration/production into a “disable-js” directory and test the changes there, the tests are pretty straight-forward if you look into the directory 👍

@timneutkens
Copy link
Member Author

@TxHawks what about using puppeteer and disabling JS?

We use the “wd” package in the Next.js test suite as we test multiple browsers (IE11, safari, Firefox, chrome) hence why puppeteer is not used

@TxHawks
Copy link
Contributor

TxHawks commented May 2, 2020

@timneutkens Pull request submitted in #12406, including initial tests

@tvararu
Copy link

tvararu commented May 11, 2020

Nice! This is how I was doing this before (+ removing <NextScript />): https://github.com/tvararu/blog.vararu.org/blob/3ec6073a22baba67835dca0f6a6397fc68a6ee9f/pages/_document.js#L3-L14

As you say, this always felt wrong because it was overriding internals. I'm excited to see where this leads.

grantheaslip added a commit to grantheaslip/website that referenced this pull request May 12, 2020
* Remove tsconfig-paths-webpack-plugin since Next.js added native support for
tsconfig.json baseUrl
(https://nextjs.org/blog/next-9-4#absolute-imports-and-aliases)

* Replace hacky _document.tsx overrides to remove client-side JS with
config.unstable_runtimeJS (vercel/next.js#11949)
@mathiasha
Copy link

@timneutkens Thanks for making this. It is exactly what i was looking for.

@gaven
Copy link

gaven commented Jun 1, 2020

@timneutkens Above you stated the following:

Do note that in this implementation it means no runtime JavaScript will be executed so you won't be able to use React hooks, event listeners etc without injecting custom <script> tags into the rendered html (eg through next/head) for pages that use unstable_runtimeJS: false

Is there an example of this in the Next.js examples? I'm under the impression that one would need to:

  1. Create a new entry point for webpack to process the aforementioned file
  2. Read the the output from the webpack manifest
  3. Insert the script using next/head - referencing the file + hash created by webpack.

Thanks Tim for all the work you've put into this.

@timneutkens
Copy link
Member Author

Is there an example of this in the Next.js examples

As this is experimental and not complete (as per my initial PR message) so we're not adding examples for it in the examples directory in case the implementation changes. However as said:

Not adding fixes right now as this implementation needs more work. If anyone wants to work on this feel free to reach out on twitter.com/timneutkens

I only built the part where JavaScript is deleted, not the part where you can add it back with compilation. Currently you can add a script tag from e.g. the public directory and that works. It'd be a good iteration of the feature to work on a way to get a compiled script though. However this does make things more complex. For example the webpack runtime would have to be shipped to the browser. Feel free to reach out if you want to work on that.

@yankustefan
Copy link

yankustefan commented Jun 18, 2020

Hello @timneutkens
Thank you for your work on this. It's much appreciated!

I just wanted to chip in my thoughts regarding the readiness for stable release of this feature.
In my opinion, it would be ready when it reliably does what it advertises: removing the javascript.

As you say, it would be a good iteration of the feature to get a compiled script though. But that's maybe a nice to have for a next version, or iteration as you say.

For a first version, it would be "perfect" just to remove javascript. And if any is needed, add those scripts via the public directory, as one already can. Yes, those would need to be compiled separately, but I think that's totally fine.

For my use case, a content heavy blog site, I really love nextjs and react to build my pages. But it is very content heavy without much "interaction". So I thought having a full blown react app on the front-end might not be necessary and maybe just add one or two lightweight preact 'widgets' at pin-point locations with preact-habitat. Since I can reuse components done in the nextjs environment, there's not much extra work and I don't mind having to compile them separately. Since I am using a different framework, I probably would have to anyway.

What do you think about going stable with a minimum viable product and "just" focussing on removing runtimeJs?

@timneutkens
Copy link
Member Author

What do you think about going stable with a minimum viable product and "just" focussing on removing runtimeJs?

This does not match our long-term vision for the feature (as described in my PR message) and would cause breaking changes (or having to maintain this feature as-is forever), both cases are losses as it would mean we then have to deprecate/replace existing stuff and cause overall churn that is not needed.
This feature even as-is is not ready, it does not reflect correctly in development mode where JS is still loaded, which would cause you to write interactions that do not work in production.

Hence why I said in my initial message:

Not adding fixes right now as this implementation needs more work. If anyone wants to work on this feel free to reach out on twitter.com/timneutkens

The initial PR was just to show what parts have to be touched and allow users to try it out if they really want to get rid of JS

@yankustefan
Copy link

Thanks for your feedback Tim.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants