skip to Main Content

I’m building an application using React an Apollo 2 and I’m trying to write a script to generate static html files for server-side rendering.

I’m currently using this logic to render my React component on the server and it’s working fine:

export default (Page) => (req, res) => {
    const App = (
        <ApolloProvider client={client(fetch)}>
            <Main>
                <Page />
            </Main>
        </ApolloProvider>
    );

    getDataFromTree(App).then(() => {
        const data = {
            html: ReactDOMServer.renderToString(App),
            seo: { title: "Home", description: "" }
        };
        res.render("index", { data });
    });
}

But I wanted to generate the .html files so I could serve them statically, without running this method every time. I tried to compile the result of renderToString to an .ejs file and then write to a file using fs, but it didn’t work. This is the code:

const component = ReactDOMServer.renderToString(App);
const template = ejs.compile(fs.readFileSync(path.join(__dirname, 'landing_template.ejs'), 'utf-8'));
const html = template({ component });

fs.writeFile(
    path.join(__dirname, "../views/landing.ejs"),
    html, 
    err => {
        if(err) console.log(err);
        else console.log("done.");
});

The file was written successfully but If I open my Page Source the part added by the compile method is grayed out and seems to be ignored:

enter image description here

I also tried just reading the file and replacing the string using .replace and inserting a dummy div, but it’s always the same result.

// using compile
const template = ejs.compile(fs.readFileSync(path.join(__dirname, 'landing_template.ejs'), 'utf-8'));
const html = template({ component });

const template = ejs.compile(fs.readFileSync(path.join(__dirname, 'landing_template.ejs'), 'utf-8'));
const html = template({ component: "<div>teste</div>" });

// using readFile
const template = fs.readFileSync(path.join(__dirname, 'landing_template.ejs'), 'utf-8');
const html = template.replace("<%- component %>", component);

const template = fs.readFileSync(path.join(__dirname, 'landing_template.ejs'), 'utf-8');
const html = template.replace("<%- component %>", "<div>teste</div>");

// maybe this is the problem?
fs.writeFile(
    path.join(__dirname, "../views/landing.ejs"),
    html,
    { encoding: "utf8" },
    err => {
        if(err) console.log(err);
        else console.log("done.");
});

enter image description here

I guess the browser is not recognizing this new text as HTML and failing to parse it. Does any one know a way I can make this work?

Thanks!

2

Answers


  1. What a cool idea! This is probably happening because, as you mentioned, the browser is not recognizing the fact that you are sending html. Try explicitly setting the Content-Type header to text/html before calling render:

    export default (Page) => (req, res) => {
        const App = (
            <ApolloProvider client={client(fetch)}>
                <Main>
                    <Page />
                </Main>
            </ApolloProvider>
        );
    
        getDataFromTree(App).then(() => {
            const data = {
                html: ReactDOMServer.renderToString(App),
                seo: { title: "Home", description: "" }
            };
            res.setHeader("Content-Type", "text/html");
            res.render("index", { data });
        });
    }
    

    Edit:

    Ah I see it now! You cannot self-close <script> tags in HTML unfortunately, and this is tripping the browser up. Close the script tag in your header like so:

    <script async src="/build/app.bundle.js" type="text/javascript"></script>
    

    That should fix the problem.

    Login or Signup to reply.
  2. <script /> is wrong. You have to use this:

    <script src="URL"></script>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search