-
Notifications
You must be signed in to change notification settings - Fork 48.5k
Render component only on the server, without "mounting" on the client #6985
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
Comments
PS. But beware of checksum-mismatches, they will recreate the DOM and ruin the magic trick. |
mhhh, the html should be rendered and visible when fetching the page already, and should also be there if the component mounts. React should just not try to render the component again on client side. I would maybe expect some hook to overwrite, like |
@BowlingX Theoretically there could be a Another possibility is to do what I said above, but keep the GUID as an ID on the node. Before mounting React client-side you (theoretically) find the node with the GUID, store it and unparent it from the DOM. Then let the component move it back inside itself upon render (by GUID), this would also survive checksum-mismatches. IMHO although a bit quirky it is probably the only in a sense proper solution to this problem and implemented right would also allow it to work transparently client-side as well. |
I tried this approach, but the checksum is somehow still invalid (on the most outer component), I'm removing all staticly generated html nodes of the component before react boots and reinsert them on // during application setup, before react boots:
global.__CMS_CONTENT__ = Array.prototype.slice.call(
global.document.querySelectorAll(`[data-cms-id]`)
).reduce((first, next) => {
const node = next;
const container = first[next.getAttribute('data-cms-id')] = [];
while (node.firstChild) {
container.push(node.firstChild);
node.removeChild(node.firstChild);
}
return first;
}, {}); /**
* @flow
*/
import React, { Component } from 'react';
type Props = {
id:string,
className?:string
}
type State = {
markup:Object
}
export default class CmsBlock extends Component<void, Props, State> {
props:Props;
state:State;
constructor(props:Props) {
super(props);
const { id } = props;
if(!global.document) {
const createMarkup = contentId => {
return {
__html: global.__CMS_CONTENT__ && global.__CMS_CONTENT__[contentId] ? global.__CMS_CONTENT__[contentId] :
`CMS Block ID <strong>${contentId}</strong> does not exist.`
};
};
this.state = {
markup: createMarkup(id)
};
return;
}
this.state = {
markup: null
}
}
parent:Element;
componentDidMount() {
global.__CMS_CONTENT__[this.props.id].forEach(childs => {
this.parent.appendChild(childs);
});
}
render() {
const { className, id } = this.props;
return (
<div ref={parent => (this.parent = parent)}
data-cms-id={id} className={className} dangerouslySetInnerHTML={this.state.markup}/>
);
}
} |
The Error is:
and the structure (simplified) looks like this: <div class="RootContainer">
<CmsBlock id="anId"/>
</div> Before react would complain on the CmsBlock Element like this:
|
Can you elaborate on exactly what your problem is? You are trying to save bandwidth by only sending the markup down to the client once?
In general, I think this is an antipattern. You should find a way for this to not be true. Anything you do to make this happen will be a hack at best (fragile and unsupported). However, if I'm understanding your issue correctly, there are potentially other ways to solve this issue. For instance, you could store the SSR markup as a string. Use that string to immediately populate an empty DOM node (eg. a |
@jimfb yeah, the idea is to save bandwidth and the markup should be correctly rendered without the need to have javascript enabled (for SEO and fallback purposes). I understand this looks like a hacky way but on the other hand, what about a lot of static generated markup that won't be touched, why should this be send twice to the client? |
Yeah, I agree. When a page has lots of static content, sending down all the code to regenerate that static content seems like a crying shame. One mitigating factor is that you've already got the content in front of the user, so the background loading of data is somewhat less of a problem. The generalized problem is: How can we avoid sending down static code/data that is never needed because it will never change after initial render. How could we load components/data only when absolutely necessary. I think there could be some good research opportunities in this area, especially if/when we switch to an incremental renderer. However, I don't know of any short-term solutions that aren't horrible hacks/antipatterns. |
I'm going to close this out, because I don't see anything actionable here. Feel free to continue the discussion on this thread, and we can re-open if we're able to come up with an action plan. |
So I just created a prototype for a solution to this problem. The basic idea is that you render the component twice on the server-side (once to get the html, once to get the checksum). For example, I have a The solution is this:
When ReactDOM.render is called on the client. It will think (incorrectly, but that's good) that the DOM matches the props you gave it because the checksum matches a render without props. Because of this it will not remove the server-rendered RawHtml content from the page, and it will not give a checksum error. Also, because shouldComponentUpdate returns false, it never removes the content unless that component is fully unmounted. So far I only see two problems with this:
Anybody see any other problems with this? |
Why is this so complicated? And why hasn't this come up more often? Isn't it a general use case to not rerender a component on client-side when it comes rendered as markup from the server? As I see it, there are three benefits of using server-side rendering:
So, we still have 1. and 2., which is great, because they are the most important in my opinion. |
I don't know if this pertains to the problem I am facing at the moment: I am rendering some static content on the server, which is basically a lot of text. The React component expects the text to be on the |
@spudly Could we render htmlWithoutProps on a server bootstrap step and cache the data-react-checksum for subsequent renders thus avoiding the double rendering. Point 2 is concerning indeed. Any chance you can share your prototype? Interesting that there doesn't seem to be much on this topic as, to me, some content is static (or really slowly changing) and it seems very wasteful to be sending these payloads twice on initial load. Obviously the legacy CMS use cases (already mentioned) but also the case with Headless CMS and the ability to create an application on top of it without compromising the initial load performance. A lot of effort goes into server side rendering the applications and optimising code and to me this is crucial to the critical rendering path. If shouldComponentUpdate was called on the initial render one could cancel or delay any rendering right? Obviously that would potentially break a lot of code but the component lifecycle sounds like the right place to me. Still a bit new to React though so apologies if I'm missing something. |
Personally I do not understand why the problem of double loading is so underestimated. There is a video where the Facebook team tries to implement a new engine to enhance rendering for a page, say from 100 ms to 60 ms. It looks like team spends time in an attempt to speed up a cheetah while there are two gigantic hippos behind it and everybody should wait for the hippos. Then we (developers) spend time to create a fix with a hack or spend our clients time to wait for a loading with duplicated data. For example, there is a need from one of our customers to create a big static page with two dynamic elements. The UI itself is not simple, so it is great to build it with reusable React components. Also there is no need to make a JSON request to a server for texts and images after loading (as a workaround), the content and UI is unique and mostly static. Now the React page is rendered on the server side and is sent to the client, at the same time React client.js contains the copy of this huge data. This is a very common scenario and the really important problem. Can someone please specify what exact issues the ReactJS team faces/foresees in attempt to resolve this problem? A proposal for a client.js:
In the source code, which will be initially rendered by a server
In this way it is possible to mark static blocks that will never change. React can restore these points to Virtual DOM (or whatever) from DOM before the first rendering. Even with stateful components and dynamic properties it is still possible to do:
On the client side we again can restore the component as it would be the first place of truth. Later it might be possible to write an algorithm which can automatically understand what part is static without manual marking. [Server] React is an amazing library, really hope to see even initial/partial solution or good suggestions. P.S. Should we rename or create a new issue with proper name? Doubled data is the real problem, not a component mounting. |
Hate to pitch in with a me-too, but I was pointed to this discussion after encountering the same problem in my ClojureScript/React app. I use server-side rendering, which works great, except for pages with lots of static content. I was hoping I could avoid transferring all the static content twice: after all, it is already in the DOM and will never change. I hope this will eventually become a more visible problem once more people start encountering the same limitation. |
@jwr It is certainly possible to do this, but it means that the client has to perform different rendering logic from the server, i.e. the server component renders some initial HTML but the client component should then provide a value "don't change". That's not really a big problem, you can hack this yourself quite easily #6985 (comment). |
we have the same problem. we would like to have the possibility to just skip rendering a component which has already been rendered on server side. |
I have a similar problem. I have a large table (5000 rows) rendered on the server. i don't want to send the data to the client twice (in HTML and JSON form) I came up with this solution/hack. Before rendering the table rows, the component first checks the DOM to see if a component exists with the same id (which if pre-rendered server-side will exist), if it does then it extracts the HTML and uses that directly via renderTable() {
debug('render table');
const id = 'table-body';
const html = this.getExistingHtml(id);
if (html) {
return <tbody id={ id } dangerouslySetInnerHTML={{ __html: html }} />;
}
return <tbody id={ id }>{ this.renderItems() }</tbody>;
}
getExistingHtml(id) {
if (typeof document === 'undefined') return;
const node = document.getElementById(id);
return node && node.innerHTML;
}
renderItems() {
debug('render items');
return this.props.items.map(({ name, url }) => {
return (
<tr>
<td>
<a href={ url }>
<div>{ name }</div>
</a>
</td>
</tr>
);
});
}
shouldComponentUpdate() {
return false;
} I've also coupled this with |
We've made progress on this. https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html |
Is it possible to have a "frozen" component, that is rendered only on server-side, without being processed on the client side? (without calling the render-cycle again).
Let's say I have a CMS, that statically generates HTML on the server (a legacy system), but I want to use react to render the output inside my component structure.
To prevent the HTML to be exposed twice, I will provide the information only on the server side.
The HTML can be quite big, and I don't want to have them on the client site again.
Right now, I don't see a way to tell react, to prevent calling the render method on a component again.
What I did, is this ugly workaround (react complains about a checksum mismatch though):
I used React 15.1.0.
The text was updated successfully, but these errors were encountered: