Hi there.
We are testing the implementation of Shopify Hydrogen on Netlify: https://hydrogen-horando-test.netlify.app/stream
What we expected was a barrier-free running instance of SSR React with Suspense functionality. While testing locally, our Suspense components were running like expected.
But deployed on Netlify we don’t see any fallback components running while the Suspense component gets streamed from the server.
We have made screenrecordings, to show what we expect and what Netlify in the end delivers. In the Suspense Netlify video you can clearly see, that it waits until all components are loaded, which is not how Suspense should work:
Suspense localhost: Loom | Free Screen & Video Recording Software | Loom
Suspense Netlify: Loom | Free Screen & Video Recording Software | Loom
Now here comes the key question: are Netlify edge functions not supporting SSR streams? The docs state yes (Hydrogen on Netlify | Netlify Docs) but the behavior is differently?!
In case you ask…here is the code:
stream.server.jsx
import { Suspense } from 'react';
import { fetchSync, useQuery } from '@shopify/hydrogen';
import { TestSuspenseComponent } from '~/components/TestSuspenseComponent';
import { TestSuspenseClientComponent } from '~/components/TestSuspenseClientComponent.client';
export default function Stream() {
return (
<>
<ul className='list-disc m-12'>
<TestSuspenseClientComponent />
<TestSuspenseComponent />
{/* Suspense in a Server component with fetchSync */}
<Suspense fallback={<LoadingFallback />}>
<SuspenseFetchSyncContent />
</Suspense>
{/* Suspense in a Server component with useQuery */}
<Suspense fallback={<LoadingFallback />}>
<SuspenseUseQueryContent />
</Suspense>
</ul>
</>
);
}
function LoadingFallback() {
return <li>Loading...</li>;
}
function SuspenseFetchSyncContent() {
const response = fetchSync(
'https://deelay.me/7000/https://jsonplaceholder.typicode.com/todos/1',
{
preload: true
}
);
let result;
if (response.ok) {
result = response.json();
} else {
console.error(`Unable to load`);
}
return <li>{result.title} (Server component delay 7000ms)</li>;
}
function SuspenseUseQueryContent() {
const { data } = useQuery(
'test-usequery',
async () => {
const response = await fetch(
'https://deelay.me/9000/https://jsonplaceholder.typicode.com/todos/2',
{
headers: {
accept: 'application/json',
},
}
);
return await response.json();
},
{
preload: true
}
);
return <li>{data.title} (Server component delay 9000ms)</li>;
}
TestSuspenseClientComponent.client.jsx
import { Suspense } from 'react';
import { fetchSync } from '@shopify/hydrogen';
export const TestSuspenseClientComponent = () => {
return (
<>
<Suspense fallback={<LoadingFallback />}>
<SuspenseFetchSyncContent />
</Suspense>
</>
);
}
function LoadingFallback() {
return <li>Loading...</li>;
}
function SuspenseFetchSyncContent() {
const response = fetchSync(
'https://deelay.me/3000/https://jsonplaceholder.typicode.com/todos/3'
);
let result;
if (response.ok) {
result = response.json();
} else {
console.error(`Unable to load`);
}
return <li>{result.title} (Client component delay 3000ms)</li>;
}
TestSuspenseComponent.jsx
import { Suspense } from 'react';
import { fetchSync } from '@shopify/hydrogen';
export const TestSuspenseComponent = () => {
return (
<>
<Suspense fallback={<LoadingFallback />}>
<SuspenseFetchSyncContent />
</Suspense>
</>
);
}
function LoadingFallback() {
return <li>Loading...</li>;
}
function SuspenseFetchSyncContent() {
const response = fetchSync(
'https://deelay.me/5000/https://jsonplaceholder.typicode.com/todos/3'
);
let result;
if (response.ok) {
result = response.json();
} else {
console.error(`Unable to load`);
}
return <li>{result.title} (Hybrid component delay 5000ms)</li>;
}