Skip to content

Instantly share code, notes, and snippets.

@matths
Created January 14, 2026 18:04
Show Gist options
  • Select an option

  • Save matths/4e8fbf97c450920837a141b53b56b014 to your computer and use it in GitHub Desktop.

Select an option

Save matths/4e8fbf97c450920837a141b53b56b014 to your computer and use it in GitHub Desktop.
Hydrate a Svelte SSR result in client
import { rollup } from 'rollup';
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
export const rollupBundle = async (target: 'server' | 'client', source: string, clientEntry?: string) => {
return await rollup({
input: clientEntry ? 'client-entry.js' : 'entry.svelte',
onwarn(warning, warn) {
if (
warning.code === 'CIRCULAR_DEPENDENCY' &&
warning.message.includes('svelte')
) {
return;
}
warn(warning);
},
plugins: [
{
name: 'resolve-files',
resolveId(id) {
if (id === 'client-entry.js') return id;
if (id === 'entry.svelte') return id;
},
load(id) {
if (id === 'client-entry.js' && clientEntry) return clientEntry;
if (id === 'entry.svelte' && source) return source;
}
},
svelte({
compilerOptions: {
generate: (() => {
console.log('Generating for', target);
return target;
})(),
runes: true,
}
}),
resolve()
]
});
}
import { render } from 'svelte/server';
import { rollupBundle } from './rollup-bundle.js';
import fs from 'fs/promises';
import path from 'path';
const source = `
<script>
let { name = '' } = $props();
</script>
<h1>Hello {name}!</h1>
`;
const clientEntry = `import { hydrate } from 'svelte';
import App from 'entry.svelte';
export { hydrate, App };
`;
const buildClient = async () => {
const bundle = await rollupBundle('client', source, clientEntry);
const { output } = await bundle.generate({
format: 'esm'
});
const clientCode = output[0].code;
return clientCode;
};
const buildSSR = async (
{ name }: { name: string }
): Promise<string> => {
const clientCodeForHydration = await buildClient();
const encodedClientCode = encodeURIComponent(clientCodeForHydration).replace(/'/g, "\\'");
const ssrBundle = await rollupBundle('server', source);
const { output } = await ssrBundle.generate({ format: 'esm' });
const code = output[0].code;
const cwd = process.cwd();
const tmpDir = path.join(cwd, 'tmp');
const tmpPath = path.join(tmpDir, `svelte-ssr-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
await fs.writeFile(tmpPath, code);
const SSRModule = await import(`file://${tmpPath}`);
const SSRComponent = SSRModule.default;
const { head, body } = render(SSRComponent, { props: { name } });
await fs.unlink(tmpPath);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Svelte SSR</title>
${head}
</head>
<body>
<div id="app">${body}</div>
<script type="module">
import { App, hydrate } from 'data:text/javascript;charset=utf-8,${encodedClientCode}';
const app = hydrate(App, {
target: document.getElementById('app'),
props: ${JSON.stringify({ name })}
});
</script>
</body>
</html>
`;
};
export default buildSSR;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment