1

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!

Thiago Loddi
  • 2,212
  • 4
  • 21
  • 35
  • 1
    I think the problem with the HTML code is the – raul.vila Apr 14 '18 at 19:25
  • script tags are not self closing tags. Even if there isn't anything in between the `` you cannot just use `` – scrappedcola Apr 14 '18 at 19:30

2 Answers2

2

<script /> is wrong. You have to use this:

<script src="URL"></script>
jiwopene
  • 3,077
  • 17
  • 30
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.

Alex
  • 64,178
  • 48
  • 151
  • 180