Hey @dyelax
Netlify functions are tough to provide support for because they can do literally anything they’re like Play-doh. So given that the Function is running, @luke can’t provide much support beyond that. Good news is there’s a team of folks here on the Community that love digging in deeper than the official support engineers can
So for starters, I did hit https://dyelax-ssr.netlify.app/.netlify/functions/default from my CLI and got a 200 but no body content (seeing this on browser too). I think that may be because you’re not using the async
tag on your function but still returning an object (the response object) instead of a callback function, which would be the correct way to do it if you had the async tag. So from:
exports.handler = (event, context) => {
to
exports.handler = async (event, context) => {
I’d also recommend making sure to set the response headers to the “text/html” MIME type
The other thing that’s important to talk through with React’s ReactDOMServer.renderToString()
=> ReactDOM.hydrate()
workflow is what renders when and what changes. Unfortunately it’s really hard to find specifics on the technical details for that workflow, but the gist is this: when you render to string on the DOMServer, React builds and renders all of your components using the default state defined in useState(<this>)
, does not run effects, and prints the HTML as such for the tree, leaving behind certain extra nodes and hook-points for when React on the client hydrates.
When React on the client it goes ahead and runs effects and other async things, then/and attaches to all of the virtual DOM nodes in order to prep for user interaction. It does that really fast, but the one thing that Hydrate
assumes is that it doesn’t need to change the HTML tree at all if you don’t change state in one of your effects. Meaning that if the tree was printed with “Not Hydrated” directly in the HTML and not as a result of an effect or state, then by contract of API, React on the client is not obligated to update that HTML. Not unless you change state - which always re-renders a component.
As a corollary / proof of concept for this exact workflow, I’ll point you to something I actually wrote for React-using Netlify’ers - react-ssg-netlify-forms
(a turn-key integration for running Netlify forms on Gatsby or Next) but it operates on the same principle - it renders one thing when rendered by ReactDOMServer and then switches over once it’s running in the window.
From react-ssg-netlify-forms/index.js at master · jon-sully/react-ssg-netlify-forms · GitHub (the React component itself):
// Build determination
const [inNetlifyBuild, setInNetlifyBuild] = useState(true)
useEffect(() => {
setInNetlifyBuild(false)
}, [])
and then down in the render itself:
return (
inNetlifyBuild
? <ServerComponent>
: <ClientComponent>
)
So since I know the component will always be rendered by DOMServer then hydrated, I use inNetlifyBuild=true
as the default state (so <ServerComponent>
is rendered) then when it’s hydrated client-side, the useEffect
will run, change the state over, and <ClientComponent>
will be rendered instead.
In the case of my library, the component swap is non-visual so nobody’s the wiser, but you can see a very brief flash of your server-rendered component or text if it’s a visual change. Usually that really is just a flash - maybe a few ms, but keep that in mind as necessary.
All that to say, I’d urge you to refactor
const App = props => {
let content = typeof window === 'undefined' ? 'NOT hydrated' : 'hydrated';
return <p>{content}</p>
}
to
const App = props => {
[onClient, setOnClient] = useState(false)
useEffect(() => {
setOnClient(true)
})
return (
onClient
? <p>Hydrated!</p>
: <p>Not Hydrated :(</p>
)
}
I hope all of that helps!
–
Jon