Skip to content

Instantly share code, notes, and snippets.

@jbreuer
Last active October 30, 2025 07:48
Show Gist options
  • Select an option

  • Save jbreuer/4390df750e8e15ad73c8c6d7fa63bbb7 to your computer and use it in GitHub Desktop.

Select an option

Save jbreuer/4390df750e8e15ad73c8c6d7fa63bbb7 to your computer and use it in GitHub Desktop.
Progressive loading on XM Cloud: beyond the Hybrid Placeholder
// Async Server Component
async function DataComponent({ title, text }) {
const response = await fetch('/api/data');
const data = await response.json();
return (
<div>
<h3>{title}</h3>
<p>{data.date}</p>
<div dangerouslySetInnerHTML={{__html: text}} />
</div>
);
}
// Page component
export default function Page() {
return (
<Suspense fallback={<p>Loading...</p>}>
<DataComponent title="Example" text="<p>Content</p>" />
</Suspense>
);
}
export const getServerSideProps = async () => {
const response = await fetch('/api/data');
const data = await response.json();
return { props: { data } };
};
const Component = ({ data, title, text }) => {
return (
<div>
<h3>{title}</h3>
<p>{data.date}</p>
<div dangerouslySetInnerHTML={{__html: text}} />
</div>
);
};
const Component = ({ title, text }) => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(result => {
setData(result);
setIsLoading(false);
});
}, []);
return (
<div>
<h3>{title}</h3>
{isLoading && <p>Loading...</p>}
{!isLoading && <p>{data.date}</p>}
<div dangerouslySetInnerHTML={{__html: text}} />
</div>
);
};
// Helper to detect navigation type
export function isClientNavigation(context) {
return !!context.req?.headers['x-nextjs-data'];
}
// Server-side logic
export const getServerSideProps = async (context) => {
// Skip SSR on client navigation
if (isClientNavigation(context)) {
return { props: { data: null } };
}
// Fetch server-side on direct load
const response = await fetch('/api/data');
const data = await response.json();
return { props: { data } };
};
// Component
const Component = ({ data: initialData, title, text }) => {
const [data, setData] = useState(initialData);
const [isLoading, setIsLoading] = useState(initialData === null);
useEffect(() => {
if (!data) {
fetch('/api/data')
.then(res => res.json())
.then(result => {
setData(result);
setIsLoading(false);
});
}
}, [data]);
return (
<div>
<h3>{title}</h3>
{isLoading && <p>Loading...</p>}
{!isLoading && <p>{data.date}</p>}
<div dangerouslySetInnerHTML={{__html: text}} />
</div>
);
};
@jbreuer
Copy link
Author

jbreuer commented Oct 30, 2025

Full explanation and comparison of all 4 patterns in my blog: https://www.jeroenbreuer.nl/blog/progressive-loading-on-xm-cloud-beyond-the-hybrid-placeholder/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment