Skip to content

Commit 975185c

Browse files
authored
docs: enable SSR prerendering for docs website (#15)
1 parent 2b8a42f commit 975185c

7 files changed

Lines changed: 94 additions & 9 deletions

File tree

.github/workflows/docs.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,7 @@ jobs:
3535
working-directory: docs-website
3636
run: npm ci
3737

38-
- name: Build ReScript
39-
working-directory: docs-website
40-
run: npm run res:build
41-
42-
- name: Build Vite
38+
- name: Build
4339
working-directory: docs-website
4440
run: npm run build
4541

@@ -49,7 +45,7 @@ jobs:
4945
- name: Upload artifact
5046
uses: actions/upload-pages-artifact@v3
5147
with:
52-
path: docs-website/dist
48+
path: docs-website/build/client
5349

5450
deploy:
5551
name: Deploy

docs-website/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
node_modules
22
dist
3+
build
34
.vite
45
*.res.mjs
56
lib

docs-website/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</head>
1414
<body>
1515
<a href="#main-content" class="skip-to-content">Skip to content</a>
16-
<div id="app"></div>
16+
<div id="app"><!--ssr-outlet--></div>
1717
<script type="module" src="/src/Main.res.mjs"></script>
1818
</body>
1919
</html>

docs-website/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"type": "module",
66
"scripts": {
77
"dev": "rescript -w & vite",
8-
"build": "rescript && vite build",
8+
"build": "npm run res:build && npm run build:client && npm run build:server && npm run build:prerender",
9+
"build:client": "vite build",
10+
"build:server": "vite build --ssr src/EntryServer.res.mjs --outDir build/server",
11+
"build:prerender": "node scripts/prerender.mjs",
912
"preview": "vite preview",
1013
"res:build": "rescript",
1114
"res:watch": "rescript -w",

docs-website/scripts/prerender.mjs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Pre-render all docs website routes to static HTML files.
3+
*
4+
* This script runs after `vite build` (client + server bundles) and uses
5+
* the SSR server entry to render each route into its own index.html file,
6+
* making the site compatible with static hosting (e.g., GitHub Pages).
7+
*
8+
* Usage: node scripts/prerender.mjs
9+
*/
10+
import fs from 'node:fs'
11+
import path from 'node:path'
12+
import { fileURLToPath } from 'node:url'
13+
14+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
15+
const buildDir = path.join(__dirname, '..', 'build', 'client')
16+
17+
// All routes to pre-render (app-relative paths, without base path)
18+
const routes = [
19+
'/',
20+
'/getting-started',
21+
'/api/signal',
22+
'/api/computed',
23+
'/api/effect',
24+
'/examples',
25+
'/release-notes',
26+
]
27+
28+
// Suppress expected SSR errors from client-only code
29+
process.on('uncaughtException', (err) => {
30+
if (err.message?.includes('document is not defined') ||
31+
err.message?.includes('window is not defined')) {
32+
return
33+
}
34+
console.error('Uncaught exception:', err)
35+
process.exit(1)
36+
})
37+
38+
async function prerender() {
39+
// Read the built index.html template (produced by vite build)
40+
const template = fs.readFileSync(path.join(buildDir, 'index.html'), 'utf-8')
41+
42+
// Load the SSR server entry (built by vite build --ssr)
43+
const { render } = await import('../build/server/EntryServer.res.js')
44+
45+
console.log(`Pre-rendering ${routes.length} routes...\n`)
46+
47+
for (const route of routes) {
48+
// Render the app HTML for this route
49+
const appHtml = render(route)
50+
51+
// Inject into the template
52+
const html = template.replace('<!--ssr-outlet-->', appHtml)
53+
54+
// Write to the correct directory structure
55+
// e.g., "/" → build/client/index.html (already exists, overwrite)
56+
// "/getting-started" → build/client/getting-started/index.html
57+
const filePath = route === '/'
58+
? path.join(buildDir, 'index.html')
59+
: path.join(buildDir, route, 'index.html')
60+
61+
// Ensure directory exists
62+
const dir = path.dirname(filePath)
63+
fs.mkdirSync(dir, { recursive: true })
64+
65+
fs.writeFileSync(filePath, html)
66+
console.log(` ${route}${path.relative(buildDir, filePath)}`)
67+
}
68+
69+
console.log('\nPre-rendering complete!')
70+
}
71+
72+
prerender()

docs-website/src/EntryServer.res

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
open Xote
2+
3+
// Import modules to ensure they are included in the bundle
4+
module Website = Website
5+
6+
// Render function called by the SSR server for each request
7+
let render = (url: string) => {
8+
// Initialize router for server-side rendering with the requested URL
9+
Router.initSSR(~basePath="/rescript-signals", ~pathname=url, ())
10+
11+
// Render the app to an HTML string
12+
SSR.renderToString(() => <Website.App />)
13+
}

docs-website/vite.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { defineConfig } from "vite"
33
export default defineConfig({
44
base: "/rescript-signals/",
55
build: {
6-
outDir: "dist",
6+
outDir: "build/client",
77
emptyOutDir: true,
88
},
99
server: {

0 commit comments

Comments
 (0)