1

I'm began to use Suspense to my react app and then I turned it to SSR, but while reading the docs: https://reactjs.org/docs/react-dom-server.html#rendertopipeablestream

I do not see anywhere how to use a custom HTML, before we used to replace the div#root to the renderToString() and you could add the title of the document and meta tags, now, with that function I only see how to return the html string from <App /> with the renderToPipeableStream function:

const stream = renderToPipeableStream(
    <StaticRouter location={req.url}>
        <App
            data={json}
            pathLang={checkLanguage(lang) ? lang : ''}
            statusCode={status}
            cookiesAccepted={accepted}
        />
    </StaticRouter>,
    {
        onShellReady() {
            res.setHeader('Content-type', 'text/html');
            stream.pipe(res);
        },
    }
);

Is there any way to intercept the constructed html in order to place it into my index.html?

1 Answers1

2

I've just solved it by creating my own WritableStream following https://stackoverflow.com/a/70900625/6732429 this way:

// HtmlWritable.js
import {Writable} from 'stream';

class HtmlWritable extends Writable {
    chunks = [];
    html = '';

    getHtml() {
        return this.html;
    }

    _write(chunk, encoding, callback) {
        this.chunks.push(chunk);
        callback();
    }

    _final(callback) {
        this.html = Buffer.concat(this.chunks).toString();
        callback();
    }
}

export default HtmlWritable;

and implementing it like this:

// server.jsx
import HtmlWritable from './HtmlWritable';
[...]
const writable = new HtmlWritable();
const stream = renderToPipeableStream(
    <StaticRouter location={req.url}>
        <App
            data={json}
            pathLang={checkLanguage(lang) ? lang : ''}
            statusCode={status}
            cookiesAccepted={accepted}
        />
    </StaticRouter>,
    {
        onShellReady() {
            res.setHeader('Content-type', 'text/html');
            stream.pipe(writable);
        },
    }
);

writable.on('finish', () => {
    const html = writable.getHtml();
    data = data.replace('<div id="root"></div>', `<div id="root">${html}</div>`);
    resolve({data, status});
});
[...]

Where data is the return fs.readFile of index.html.

That was my solution, if you solved it different, please share!

Thank a lot and feel free to comment, I would like to know your opinion!

  • That's working, thanks! I don't get why React doesn't still provide a `getHtml` function. – Vincent Hoch-Drei Nov 23 '22 at 16:23
  • did you hydrate the root element with hydrateRoot and it worked as expected? in the React example they use hydrateRoot(document) after they stream the entire document as the React component. – Gil Mar 14 '23 at 14:55
  • @Gil you can hydrate or upload the JS you need in that specific page, I add hydation when I "compile" the project for SSR but in the compiler I do not use de "index.js" as you can see I use directly the React Components – Marc J Cabrer Mar 20 '23 at 08:42
  • Have you improved this solution over the years? Or is it still relevant today? Thx – NicoSan Jun 11 '23 at 18:54
  • @NicoSan Since It keeps working for me I still use the same solution, have you bumped into any issues? Or any better solution? Feel free to comment! – Marc J Cabrer Jun 12 '23 at 21:11
  • Sorry for my late reply, but I was on vacation. Would you have a source to share or a more detailed example? At the moment I have the error: ReferenceError: resolve is not defined. I'm also wondering about the "status" variable. Thanks in advance for your comments. – NicoSan Jun 27 '23 at 19:27