Comments (17)
I've been thinking a lot about this recently and just found this issue so I thought I'd jot some thoughts down.
This is going to be hugely important when SSR-support for Suspense hits (at least if that will include the ability to render pure html). Streaming will probably be the first class API for a new server renderer and being able to stream content early is perhaps the biggest promise for Suspense on the server side compared to today.
One huge problem that is still left to fix, especially for SEO, is precisely what this issue describes. I can see two valid solutions (that could also be combined) to streaming pure html depending on what type of requirements you have:
-
Only use something like Helmet on the client side for all regular users, but try to identify bots via User Agent on the server side and wait for entire render to finish before sending the response in those cases so they get a valid head. (You might want to serve different things anyway now that Google supports dynamic rendering, such as only server rendering above the fold for users, but server rendering everything for bots.)
-
Somehow identify when head is done and start streaming at that point.
To accomplish nr 2 I can see two different solutions, the first is something like what you posted above @wmertens and this is probably a good way to do it for a lot of cases, but if you have Helmet spread out over your application it could be tricky to know when it's done?
A second way, that has slightly different tradeoffs, is declaring up front what you expect to see declared before starting to stream, something roughly like:
const { headPromise, HelmetProvider } = createHelmetProvider({
required: {
meta: [{ name: "description" }, { name: "keywords" }]
}
});
const stream = ReactDOMServer.renderToStream(
<HelmetProvider>
<App />
</HelmetProvider>
);
helmetPromise.then(({ helmet }) => {
// Construct html+head and start streaming
});
When something has been added for a meta description and keywords, promise would resolve.
I haven't thought hard enough about the tradeoffs for the different solutions, but I don't think they exclude each other and different apps could have different usecases for this.
from react-helmet-async.
Actually, now that I think about it, since the required things are kept on the context, they could also be updated from within the rendering-process. This is nice because:
- If you have a centralised routing-config, you could co-locate the required head-stuff with your routes there and declare them all up front based on the url
- If you don't have a centralised routing config but instead declare them all dynamically within your render-tree, you could still co-locate required head-stuff and routes (as long as requirements are declared before
headPromise
has already resolved)
from react-helmet-async.
I'm coming here with the same problem as I've been facing race conditions with streaming and data-fetching in the components. I think a callback from the Helmet context to notify Helmet (that does as @wmertens solution suggests) would be super useful.
@wmertens @Ephem How are you solving for this problem today?
from react-helmet-async.
I'm not unfortunately. I've played around with it a little personally and seen a very early POC of what I described work, but nothing nearly ready to share and I'm not currently working on it. We might be tackling streaming at work Q1/Q2 so I'm still very interested in solutions to this! :)
from react-helmet-async.
from react-helmet-async.
Hi. I'm facing this problem too, let's think together.
Goal: Reduce TTFB
Problem: We don't know what head content would be until we render a closing body tag
I'm not sure if promise makes sense, if we just renderToString it would take same or less time.
The solution would be to patch output stream somehow. I've made a quick solution today, but it looks that streamReplace
actually buffers the stream, and doesn't help to reduce TTFB.
I didn't test it in live production, I'm only setting up the boilerplate for a new project.
import React from 'react';
import { renderToNodeStream, renderToString } from 'react-dom/server';
import { getDataFromTree } from 'react-apollo';
import multistream from 'multistream';
import stringStream from 'string-to-stream';
import streamReplace from 'stream-replace';
import Head from './Head';
import Html from './Html';
import App from './App';
module.exports = async (ctx) => {
ctx.set('Content-Type', 'text/html');
const app = <App context={ctx} />;
await getDataFromTree(App);
ctx.body = multistream([
stringStream('<!doctype html>'),
renderToNodeStream(<Html app={app} context={ctx} />),
]).pipe(streamReplace(
/<head.*head>/,
() => renderToString(<Head context={ctx} />),
));
};
from react-helmet-async.
@droganov ok so you are proposing emitting a string like <!--$$HELMET$$-->
in the <head>
, buffering it through a custom transform stream, and giving that custom stream the correct data once it's complete?
That sounds doable… a little wasteful due to the text scanning, but it can be implemented without changes to react nor react-async-helmet – a good start for seeing if this is worth doing.
from react-helmet-async.
...Uhm I just realized that I'm using React to render my entire HTML page including doctype and head, but that's actually not really necessary. So instead, the server could immediately send the html preamble, including link tags etc, and then render the react stream and wait for either the "done helmet" callback or the end of the stream, and pipe that to the client. No transform stream or text parsing needed.
(I do cache the SSR results in memory so I'll need to find a different way to do that, now I'm just rendering to string and caching that)
from react-helmet-async.
@wmertens if you pipe head after it could look surprising in the browser, you have to buffer
from react-helmet-async.
@droganov how do you mean? The browser would get
- send to client
<doctype...><head><link ....><etc...>
- start react render stream, pass callback via context
- wait for react to finish rendering/early callback
- send Helmet strings
- pipe react stream to client; it will have buffered the results
This could deadlock if the react stream buffer is smaller than the moment the callback is called, but other than that this is fine, no?
from react-helmet-async.
@wmertens
4 send Helmet strings — it may contain styles, title description and what ever
so how you gonna put it the to header after you sent it?
from react-helmet-async.
I didn't send the </head>
yet, only the parts of the head that are fixed (e.g. telling the browser to preload scripts). After the helmet strings are sent, send </head>
and the react stream.
from react-helmet-async.
then yes, looks realistic, start stream from some string, then buffer and continue
from react-helmet-async.
The one thing you can't do with this setup is add attributes to the html tag, obviously.
from react-helmet-async.
yes
from react-helmet-async.
I just realized that I do redirects based on react-router, and that changes the very first line of the response (HTTP/1.1 302
), so until I'm sure there's no redirects, there's nothing to send at all, and those can happen even after the Helmet calls.
So I'm thinking the complete notification shouldn't even be part of Helmet, just a signal between the app and the SSR setup. 🤔
After the signal is received, the SSR can output the headers with some random etag, the doctype, head, and the body up until the React div, then stream the react render, and finally the post-body with the scripts
from react-helmet-async.
Closing because on reflection I agree with myself at #3 (comment)
from react-helmet-async.
Related Issues (20)
- Is this project in maintainence mode? HOT 1
- Favicon not updating on Google Chrome but working fine on Mozilla Firefox HOT 3
- preview url not working HOT 1
- Optional Context break in 0.2.0 HOT 2
- No release notes HOT 6
- onload is not working because is getting rendered as string
- [2.0.1] typing issue on HelmetProvider
- `/*#__PURE__*/` annotations don't play well with Vite / Rollup HOT 3
- Vite SSG: The requested module 'react-helmet-async' does not provide an export named 'HelmetProvider' HOT 4
- Support for micro-frontends (container app with hosted apps)
- fix: The url for constants.js in the readMe file
- Module parse failed: unexpected token HOT 5
- using UNSAFE_componentWillMount in strict mode HOT 1
- Meta keywords and description are disabled
- Is there a way to specify a default title? HOT 1
- Is it possible to change meta tags dynamically while page is loading and after that share it to some socials? HOT 2
- Support to preact?
- Add Installation Section to README.md HOT 1
- g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g g'、
- Only some tags are being rendered?
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react-helmet-async.