I have a nextjs(v7) built application that we are trying to add some Edge Side Include tags to that were provided:
<esi:include src="https://example.com/head" />
<esi:include src="https://example.com/menu" />
<esi:include src="https://example.com/footer" />
We have added these to our custom _document.js (which is used by all nextjs/react compiled pages) like so:
import Document, { Head } from 'next/document'
import React from 'react'
export default class MyDocument extends Document {
render () {
const { html } = this.props
return (
<html>
<Head>
React.createElement('esi:include', { src: 'https://example.com/head }, null)}
</Head>
<body>
React.createElement('esi:include', { src: 'https://example.com/menu }, null)}
<div id='__next' dangerouslySetInnerHTML={{ __html: html }} />
React.createElement('esi:include', { src: 'https://example.com/footer }, null)}
</body>
</html>
)
}
}
The problem is that what is being compiled/transpiled is adding the closing tags to the esi:include elements and not leaving them as a void style self closing tag element:
<html>
<head>
... metas, titles
<esi:include src="https://example.com/head" class="next-head"></esi:include>
</head>
<body>
... stylesheets and preload links
<div>
<esi:include src="https://example.com/menu"></esi:include>
</div>
<div id="__next">
... main app container
<esi:include src="https://example.com/footer"></esi:include>
</div>
</body>
</html>
I thought this would probably be OK and the Edge server should hopefully recognize it just the same, but no go as the Edge Server has to have it with a self closing tag (according to section 3.1 here in the specs) to find it and do the code injection.
We have tried to use some dangerouslySetInnerHTML solutions but same thing happens, the closing tag gets added on instead of leaving the self closing tag as is. As such I suspect this is not something to do with how we are doing it in the JSX code, but happening somewhere in the webpack, babel compiling/transpiling to make it valid html5, but am not sure how to get around it. It seems that the ESI specs that require the include tag to be self closing and HTML5 specs that only allow a small specified set to be void (self-closing) elements are not compatible.
Any ideas anyone as to how I can configure Webpack or Babel to keep the self closing tags? Thanks in advance for any help!
----- PARTIAL WORK AROUND UPDATE -----
We have managed to implement a partial workaround for this issue by adding 3 static html files into the designated static folder for the express server that serves up the nextjs/react app. The 3 files (head.html, menu.html, and foot.html) are then injected by some client side javascript added to the custom _document.js like so:
import Document, { Head } from 'next/document'
import React from 'react'
export default class MyDocument extends Document {
render () {
const { html } = this.props
return (
<html>
<Head>
</Head>
<body>
<div id='__next' dangerouslySetInnerHTML={{ __html: html }} />
<script>
var esiHeadReq = new XMLHttpRequest();
esiHeadReq.open('GET', '/static/esi-head.html', true);
esiHeadReq.send(null);
esiHeadReq.onreadystatechange = function () {
if (esiHeadReq.readyState === 4) {
document.head.insertAdjacentHTML('beforeend', esiHeadReq.responseText);
}
}
var esiMenuReq = new XMLHttpRequest();
esiMenuReq.open('GET', '/static/esi-menu.html', true);
esiMenuReq.send(null);
esiMenuReq.onreadystatechange = function () {
if (esiMenuReq.readyState === 4) {
document.body.insertAdjacentHTML('afterbegin', esiMenuReq.responseText);
}
}
var esiFootReq = new XMLHttpRequest();
esiFootReq.open('GET', '/static/esi-foot.html', true);
esiFootReq.send(null);
esiFootReq.onreadystatechange = function () {
if (esiFootReq.readyState === 4) {
document.body.insertAdjacentHTML('beforeend', esiFootReq.responseText);
}
}
</script>
</body>
</html>
)
}
}
This works in that the Edge Server now correctly injects the header/menu/footer code to the unmolested (no webpack or babel transpiled) html files, but when the script we have added to insert the code into the main DOM used by the react pages it is not binding some behaviours correctly in some inserted javascript and so the menu doesn't actually work. This is likely due to adding it to the DOM using the insertAdjacentHTML after all the other rendering and JS binding is done. So still not a solution...