import { json, type LoaderFunctionArgs } from '@remix-run/cloudflare'; import { useLoaderData } from '@remix-run/react'; import { useCallback, useEffect, useRef, useState } from 'react'; const PREVIEW_CHANNEL = 'preview-updates'; export async function loader({ params }: LoaderFunctionArgs) { const previewId = params.id; if (!previewId) { throw new Response('Preview ID is required', { status: 400 }); } return json({ previewId }); } export default function WebContainerPreview() { const { previewId } = useLoaderData(); const iframeRef = useRef(null); const broadcastChannelRef = useRef(); const [previewUrl, setPreviewUrl] = useState(''); // Handle preview refresh const handleRefresh = useCallback(() => { if (iframeRef.current && previewUrl) { // Force a clean reload iframeRef.current.src = ''; requestAnimationFrame(() => { if (iframeRef.current) { iframeRef.current.src = previewUrl; } }); } }, [previewUrl]); // Notify other tabs that this preview is ready const notifyPreviewReady = useCallback(() => { if (broadcastChannelRef.current && previewUrl) { broadcastChannelRef.current.postMessage({ type: 'preview-ready', previewId, url: previewUrl, timestamp: Date.now(), }); } }, [previewId, previewUrl]); useEffect(() => { // Initialize broadcast channel broadcastChannelRef.current = new BroadcastChannel(PREVIEW_CHANNEL); // Listen for preview updates broadcastChannelRef.current.onmessage = (event) => { if (event.data.previewId === previewId) { if (event.data.type === 'refresh-preview' || event.data.type === 'file-change') { handleRefresh(); } } }; // Construct the WebContainer preview URL const url = `https://${previewId}.local-credentialless.webcontainer-api.io`; setPreviewUrl(url); // Set the iframe src if (iframeRef.current) { iframeRef.current.src = url; } // Notify other tabs that this preview is ready notifyPreviewReady(); // Cleanup return () => { broadcastChannelRef.current?.close(); }; }, [previewId, handleRefresh, notifyPreviewReady]); return (