Skip to content

Commit 7dda0d8

Browse files
docs(website): initial SSR docs
1 parent 75f41fb commit 7dda0d8

File tree

6 files changed

+354
-2
lines changed

6 files changed

+354
-2
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export default (ts) =>
2+
ts
3+
? [
4+
{
5+
fileName: 'overmind/onInitialize.ts',
6+
code: `
7+
import { OnInitialize } from 'overmind'
8+
9+
export const onInitialize: OnInitialize = ({ state }, overmind) => {
10+
const mutations = window.__OVERMIND_MUTATIONS
11+
12+
overmind.rehydrate(state, mutations)
13+
}
14+
`,
15+
},
16+
]
17+
: [
18+
{
19+
fileName: 'overmind/onInitialize.js',
20+
code: `
21+
export const onInitialize = ({ state }, overmind) => {
22+
const mutations = window.__OVERMIND_MUTATIONS
23+
24+
overmind.rehydrate(state, mutations)
25+
}
26+
`,
27+
},
28+
]
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { tsAppIndex } from '../../templates'
2+
3+
const javascript = {
4+
react: [
5+
{
6+
fileName: 'server/routePosts.js',
7+
code: `
8+
import { renderToString } from 'react-dom/server'
9+
import { createOvermindSSR } from 'overmind'
10+
import { Provider } from 'overmind-react'
11+
import { config } from '../client/overmind'
12+
import App from '../client/components/App'
13+
import db from './db'
14+
15+
export default async (req, res) => {
16+
const overmind = createOvermindSSR(overmind)
17+
18+
overmind.currentPage = 'posts'
19+
overmind.posts = await db.getPosts()
20+
21+
const html = renderToString(
22+
<Provider value={overmind}>
23+
<App />
24+
</Provider>
25+
)
26+
27+
res.send(\`
28+
<html>
29+
<body>
30+
<div id="app">\${html}</div>
31+
<script>
32+
window.__OVERMIND_MUTATIONS = \${JSON.stringify(overmind.hydrate())}
33+
</script>
34+
<script src="/scripts/app.js"></script>
35+
</body>
36+
</html>
37+
\`)
38+
}
39+
`,
40+
},
41+
],
42+
vue: [
43+
{
44+
fileName: 'server/routePosts.js',
45+
code: `
46+
import Vue from 'vue'
47+
import { createRenderer } from 'vue-server-renderer'
48+
import { createOvermindSSR } from 'overmind'
49+
import { createPlugin } from 'overmind-vue'
50+
import { config } from '../client/overmind'
51+
import App from '../client/components/App'
52+
import db from './db'
53+
54+
const renderer = createRenderer()
55+
56+
export default async (req, res) => {
57+
const overmind = createOvermindSSR(overmind)
58+
const OvermindPlugin = createPlugin(overmind)
59+
const app = new Vue({
60+
render(h) {
61+
return h(App)
62+
}
63+
})
64+
65+
Vue.use(OvermindPlugin)
66+
67+
const app =
68+
overmind.currentPage = 'posts'
69+
overmind.posts = await db.getPosts()
70+
71+
OvermindPlugin.set(overmind)
72+
73+
const html = await renderer.renderToString(app)
74+
75+
res.send(\`
76+
<html>
77+
<body>
78+
<div id="app">\${html}</div>
79+
<script>
80+
window.__OVERMIND_MUTATIONS = \${JSON.stringify(overmind.hydrate())}
81+
</script>
82+
<script src="/scripts/app.js"></script>
83+
</body>
84+
</html>
85+
\`)
86+
}
87+
`,
88+
},
89+
],
90+
}
91+
92+
const typescript = {
93+
react: [
94+
{
95+
fileName: 'server/routePosts.ts',
96+
code: `
97+
import { renderToString } from 'react-dom/server'
98+
import { createOvermindSSR } from 'overmind'
99+
import { Provider } from 'overmind-react'
100+
import { config } from '../client/overmind'
101+
import App from '../client/components/App'
102+
import db from './db'
103+
104+
export default async (req, res) => {
105+
const overmind = createOvermindSSR(overmind)
106+
107+
overmind.currentPage = 'posts'
108+
overmind.posts = await db.getPosts()
109+
110+
const html = renderToString(
111+
<Provider value={overmind}>
112+
<App />
113+
</Provider>
114+
)
115+
116+
res.send(\`
117+
<html>
118+
<body>
119+
<div id="app">\${html}</div>
120+
<script>
121+
window.__OVERMIND_MUTATIONS = \${JSON.stringify(overmind.hydrate())}
122+
</script>
123+
<script src="/scripts/app.js"></script>
124+
</body>
125+
</html>
126+
\`)
127+
}
128+
`,
129+
},
130+
],
131+
vue: javascript.vue,
132+
angular: [
133+
{
134+
fileName: 'overmind/index.ts',
135+
code: tsAppIndex(
136+
'angular',
137+
`
138+
import { state } from './state'
139+
140+
const config = {
141+
state,
142+
}
143+
`
144+
),
145+
},
146+
],
147+
}
148+
149+
export default (ts, view) => (ts ? typescript[view] : javascript[view])
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { tsAppIndex } from '../../templates'
2+
3+
const javascript = {
4+
react: [
5+
{
6+
fileName: 'overmind/index.js',
7+
code: `
8+
import { createHook } from 'overmind-react'
9+
10+
export const config = {
11+
state: {
12+
isLoadingPosts: false
13+
}
14+
}
15+
16+
export const useOvermind = createHook()
17+
`,
18+
},
19+
{
20+
fileName: 'index.js',
21+
code: `
22+
import React from 'react'
23+
import { render } from 'react-dom'
24+
import { Overmind } from 'overmind'
25+
import { Provider } from 'overmind-react'
26+
import { config } from './overmind'
27+
import App from './components/App'
28+
29+
const overmind = new Overmind(config)
30+
31+
render(
32+
<Provider value={overmind}>
33+
<App />
34+
</Provider>,
35+
document.querySelector('#app')
36+
)
37+
`,
38+
},
39+
],
40+
vue: [
41+
{
42+
fileName: 'overmind/index.js',
43+
code: `
44+
export const config = {
45+
state: {
46+
isLoadingPosts: false
47+
}
48+
}
49+
`,
50+
},
51+
{
52+
fileName: 'overmind/index.js',
53+
code: `
54+
import Vue from 'vue'
55+
import { Overmind } from 'overmind'
56+
import { createPlugin } from 'overmind-vue'
57+
import { config } from './overmind'
58+
import App from './components/App'
59+
60+
const overmind = new Overmind(config)
61+
62+
Vue.use(createPlugin(overmind))
63+
64+
new Vue({
65+
el: document.querySelector('#app'),
66+
render: h => h(App)
67+
})
68+
`,
69+
},
70+
],
71+
}
72+
73+
const typescript = {
74+
react: [
75+
{
76+
fileName: 'overmind/index.ts',
77+
code: `
78+
import { IConfig } from 'overmind'
79+
import { createHook } from 'overmind-react'
80+
import { state } from './state'
81+
82+
export const config = {
83+
state
84+
}
85+
86+
declare module 'overmind' {
87+
interface Config extends IConfig<typeof config> {}
88+
}
89+
90+
export const useOvermind = createHook<typeof config>()
91+
`,
92+
},
93+
{
94+
fileName: 'index.ts',
95+
code: `
96+
import * as React from 'react'
97+
import { render } from 'react-dom'
98+
import { Overmind } from 'overmind'
99+
import { Provider } from 'overmind-react'
100+
import { config } from './overmind'
101+
import { App } from './components/App'
102+
103+
const overmind = new Overmind(config)
104+
105+
render(
106+
<Provider value={overmind}>
107+
<App />
108+
</Provider>,
109+
document.querySelector('#app')
110+
)
111+
`,
112+
},
113+
],
114+
vue: javascript.vue,
115+
angular: [
116+
{
117+
fileName: 'overmind/index.ts',
118+
code: tsAppIndex(
119+
'angular',
120+
`
121+
import { state } from './state'
122+
123+
const config = {
124+
state,
125+
}
126+
`
127+
),
128+
},
129+
],
130+
}
131+
132+
export default (ts, view) => (ts ? typescript[view] : javascript[view])
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Server Side Rendering
2+
3+
Some projects requires you to render your application on the server. There are different reason to do this, like search engine optimizations, general optimizations and even browser support. What this means for state management is that you want to expose a version of your state on the server and render the components with that state. But that is not all, you also want to **hydrate** the changed stat and pass it to the client with the HTML so that it can **rehydrate** and make sure that when the client renders initially, it renders the same UI.
4+
5+
## Preparing the project
6+
7+
When doing server side rendering the configuration of your application will be shared by the client and the server. That means you need to structure your app to make that possible. There is really not much you need to do.
8+
9+
```marksy
10+
h(Example, { name: "guide/writingtests/structuringtheapp.ts" })
11+
```
12+
13+
Here we only export the configuration from the main Overmind file. The instantiation rather happens where we prepare the application on the client side. That means we can now safely import the configuration also on the server.
14+
15+
## Preparing effects
16+
17+
The effects will also be shared with the server. Typically this is not an issue, but you should be careful about creating effects that runs logic when they are defined. You might also consider lazy loading effects so that you avoid loading them on the server at all. You can read more about in the [running side effects guide](/http://localhost:4000/guides/beginner/04_runningsideeffects).
18+
19+
## Rendering on the server
20+
21+
When you render your application on the server you will have to create an instance of Overmind designed for running on the server. On this instance you can change the state and provide it to your components for rendering. When the components have rendered you can **hydrate** the changes and pass them a long to the client so that you can **rehydrate**.
22+
23+
```marksy
24+
h(Notice, null, "Overmind does not hydrate the state, but the mutations you performed. That means it minimizes the payload passed over the wire.")
25+
```
26+
27+
The following shows a very simple example using an [express](https://expressjs.com/) middleware to return a server side rendered version of your app.
28+
29+
```marksy
30+
h(Example, { name: "guide/serversiderendering/renderonserver.ts" })
31+
```
32+
33+
## Rehydrate on the client
34+
35+
On the client you just want to make sure that your Overmind instance rehydrates the mutations performed on the server so that when the client renders, it does so with the same state. The **onInitialize** hook of Overmind is the perfect spot to do this.
36+
37+
```marksy
38+
h(Example, { name: "guide/serversiderendering/renderonclient.ts" })
39+
```
40+
41+
```marksy
42+
h(Notice, null, "If you are using state first routing, make sure you prevent the router from firing off the initial route, as this is not needed")
43+
```

packages/overmind-website/src/components/Api/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const Api: SFC = () => {
1818
useEffect(
1919
() => {
2020
import('../../../api/' + state.currentApi + '.md').then((module) => {
21-
setContent(module)
21+
setContent(module.default)
2222
})
2323
},
2424
[state.currentApi]

packages/overmind-website/src/components/Guide/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const Guide: SFC = () => {
2121
state.currentGuide.type +
2222
'/' +
2323
state.currentGuide.title +
24-
'.md').then((module) => setContent(module))
24+
'.md').then((module) => setContent(module.default))
2525
},
2626
[state.currentGuide.type, state.currentGuide.title]
2727
)

0 commit comments

Comments
 (0)