feat(core): allow to preload additional resources before rendering a page#6637
feat(core): allow to preload additional resources before rendering a page#6637koistya wants to merge 2 commits intofacebook:mainfrom
Conversation
✅ [V2]Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify site settings. |
|
⚡️ Lighthouse report for the changes in this PR:
Lighthouse ran on https://deploy-preview-6637--docusaurus-2.netlify.app/ |
|
Have you actually tested the code of this PR? Because to me it doesn't work 😅 We use preload on the static pages with React-Loadable components, which do not take these extra args: https://github.com/jamiebuilds/react-loadable#preloading In practice, if you provide your own "preload" callback to a page component, I suspect only one of these 2 will be called:
I tried this on our own homepage: Home.preload = async function () {
console.log('Home.preload');
};Now if I run I only see react-loadable preload being called, not the homepage callback: So it seems React-Loadable doesn't "hoist" your preload function, and just overrides it somehow.
Please provide a better explanation of why you need this. What's the use-case and the expected business outcome? let price = null;
// Dynamic page component (example)
function Price({ match }) {
return (<p>{match.params.token} Price: {price}</p>);
}
// Load data from the API before rendering the page
Price.preload = async function({ match }) {
const res = await fetch(`https://rate.sx/${match.params.token}`);
price = await res.text();
};
export default Price;I'd strongly advocate against using such a code with side effects variable stored as a global. How would errors be handled exactly? Should we still navigate to the targeted page, or block the navigation? How first render would work? Where is "preload" called in this case? Imagine this scenario:
I see how this feature could be useful for Docusaurus dynamic pages, but I think it's too early to create a PR for this. We should try to adopt the render-as-you-fetch pattern of upcoming React versions, and I'd rather delay the implementation until then https://reactjs.org/docs/concurrent-mode-suspense.html#approach-3-render-as-you-fetch-using-suspense |
|
@slorber good point :) I'm trying to find a way to use Docusaurus for building single-page apps — having some dynamic routes, end being able to preload data (from API) for these dynamic routes, similarly to how Docusaurus preloads application chunks and shows a progress bar before the page renders on the screen. I see that it calls docusaurus/packages/docusaurus/src/client/preload.ts Lines 30 to 33 in aa446b7 That might be a good place to pass routing information (params) into the "loader". Next step is to figure out how to pass custom loaders into It's a bit ticker to implement with React Router (as opposed to using router optimized for pre-rendering and SSR). This approach might be a good way to go: // Dynamic page route added via a plugin
routes.addRoute({
path: "/prices/:symbol", // e.g. /price/BTC
component: "@site/src/Prices.js",
async preload({ params }) {
const res = await fetch(`https://rates.sx/${params.symbol}`);
const price = await res.text();
return { symbol: params.symbol, price };
});or, even better, support Relay queries out of the box. Or, allow Docusaurus users to implement it this way: addRoute({
path: "/prices/:ticker",
component: "@site/src/Prices.tsx",
query: graphql`
query RoutesPricesQuery($ticker: String!) {
prices(ticker) { ...Prices_data }
}
`
}@slorber Q: Is there currently a way to suspend page rendering (by using Suspend or whatnot) before the data required by the route/page was loaded from the server (for dynamic pages/routes, instead of showing Loading... screen)? |
addRoute({
path: "/price/:ticker",
component: "@site/src/Price.tsx",
// this function would be passed into the generated code → react-loadable
preload: `({ params }) => fetch(\`https://rate.sx/api/${params.ticker}\`).then(res => res.text())`
});That does not really look like a good API 😅
No good way for now no, most users are using Docusaurus for static pages. I still don't understand how you want to handle those cases exactly:
|
ed6b353 to
5f178a0
Compare
|
@slorber regarding these use cases:
It would display the "Loading" indicator in the toolbar, but the new (page) component is not rendered on the screen until all the required stuff is loaded. The exact same way it works when preloading application chunks — you don't show Loading in the middle of the screen while chunks are being loaded. We need the exact same behavior for preloading minimum necessary data from the API for same (dynamic) routes/pages.
It would render Docusaurus error page, unless the route has it's own error boundary in place.
Yeah, the API can be better, but most likely it would require replacing React Router with an alternative solution optimized for pre-rendering / SSR 😉 I'm curios to see your API proposal for this feature. |
5f178a0 to
69df4fe
Compare
69df4fe to
7f30c53
Compare
| parts.push(`${key}: ${JSON.stringify(propValue)}`); | ||
| const value = | ||
| typeof propValue === 'function' | ||
| ? propValue.toString() |
There was a problem hiding this comment.
I don't like this API. It breaks the isolation of client-server and means plugin authors can write arbitrary JS code that's executed on the client side, which can be easily error-prone. This has never been the case before, and I don't feel like it's good.
|
@koistya I'm not sure to understand the real motivation behind this feature. What are the desired business outcomes? Why is it important to achieve it? Why it's not possible without this feature? Please share a very concrete use-case. This feature looks like a potential nice to have for some UX edge cases (ie for users using dynamic pages, not a lot), but is not essential IMHO We are trying to focus on 2.0, so we can't dedicate too much time on this PR in the short term. I also suggest that you implement a POC end-2-end: please try to add this feature on a page in the _dogfooding folder (or eventually even the homepage) so that we can see the final UX that your feature would unlock. You can use a fake async callback with a random delay. It's better if we can see it in action before deciding if it's good or not on paper |
|
@slorber I see that some efforts have been made to make Docusaurus work for building SPAs (with its ability to add wildcard routes). This feature is critical to make that kind of routes work smoothly (without flashing empty or loading screen as you navigate from page to page), assuming that these routes need to fetch data from API before they can render some meaningful UI — basically, the exact same way it works with application chunk preloading. |
That's only an auxiliary benefit of exposing the whole Still, with that said, I see the benefit of being able to prefetch/preload dynamic routes. |
|
@koistya again, please provide a real use-case: show us a real page, with dynamic data, where spinners are not good. What do you want to display on that page exactly, and why exactly do you think the current Docusaurus experience is not great. Help us feel the problem you have, provide a real demo where we can see the problem that we are trying to solve in practice.
In any case, you have to render spinners for the very first load because the page must be statically rendered. The only other option is to render a blank page with a progress bar until React hydrates 🤷♂️ this is definitively not good, and spinners would rather be inlined in the HTML file. |
Literally any single-page app. Note, that we're talking specifically about wildcard routes here and/or client-side (only) page rendering. Take a look at this Docusaurus GitHub repository as an example, as you click on the navigation and go from Currently, you have this implemented for pre-loading missing application chunks before rendering the next page/route. If it would allow loading some other resources before rendering the page, that would make the wildcard routes feature more useful (whipping out the whole screen as the user goes from page to page definitely won't be a good user experience). |
Again, I don't ask for a "kind of app", but for one very concrete and specific example.
Unlike Docusaurus, GitHub is a server-rendered app. It may provide turbolinks/progress on page transitions, but it is a very different case because it never has to deal with the first load experience. GitHub never has to render spinners because the first load always contains correct HTML markup. Docusaurus will have to render spinners for dynamic pages because it can't render those pages on the server, even if they contain a wildcard and are dynamic routes. If you want this feature to land, please do what I ask:
|
|
Note, the ability to not render spinners does not entirely come from preloading, but also from |
|
this PR is not ready and React Router will soon support this feature out of the box (https://remix.run/blog/remixing-react-router) We'll want to use the RR support directly instead of ending up with our own system alongside the one RR will provide |

Motivation
Need a way to load additional resources, e.g. fetching (critical) data from a REST/GraphQL API, before rendering a page on the client side. As opposed to showing a loading indicator on the screen during data fetching. You can always combine it with progressive rendering of UI elements using React Suspend.
Approach 1:
If
src/preloadfile exists (by convention), Docusaurus would call it during client-side preloading:Approach 2:
It can be implemented by allowing to pass an optional
preloadproperty into the.addRoute(...):Have you read the Contributing Guidelines on pull requests?
Yes