You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/server-side-rendering.md
+91-14Lines changed: 91 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,12 +1,58 @@
1
-
# Five steps to enable Server-Side Rendering in your Elmish + DotNet App!
1
+
# Five steps to enable Server-Side Rendering in your [Elmish](https://github.com/fable-elmish/elmish) + [DotNet Core](https://github.com/dotnet/core) App!
2
2
3
-
[SSR Sample App of SAFE-stack template is available!](https://github.com/fable-compiler/fable-react/tree/master/Samples/SSRSample)
3
+
> [SSR Sample App](https://github.com/fable-compiler/fable-react/tree/master/Samples/SSRSample) based on [SAFE-Stack](https://github.com/SAFE-Stack/SAFE-BookStore) template is available!
4
+
5
+
## Introduction
6
+
7
+
### What is Server-Side Rendering (SSR) ?
8
+
9
+
Commonly speaking SSR means the majority of your app's code can run on both the server and the client, it is also as known as "isomorphic app" or "universal app". In React, you can render your components to html on the server side (usually a nodejs server) by `ReactDOMServer.renderToString`, reuse the server-rendered html and bind events on the client side by `React.hydrate`.
10
+
11
+
#### Props
12
+
13
+
* Better SEO, as the search engine crawlers will directly see the fully rendered page.
14
+
* Faster time-to-content, especially on slow internet or slow devices.
15
+
16
+
#### Cons
17
+
18
+
* Development constraints, browser-specific code need add compile directives to ignore in the server.
19
+
* More involved build setup and deployment requirements.
20
+
* More server-side load.
21
+
22
+
#### Conclusions
23
+
24
+
While SSR looks pretty cool, it still adds more complexity to your app, and increases server-side load. But it could be really helpful in some cases like solving SEO issue in SPAs, improving time-to-content of mobile sites, etc.
25
+
26
+
### Server-Side Rendering in fable-react
27
+
28
+
fable-react's SSR approach is a little different from those you see on the network, it is a **Pure F#** approach. It means you can render your elmish's view function directly on dotnet core, with all benefits of dotnet core runtime!
29
+
30
+
There are lots of articles about comparing dotnet core and nodejs, I will only mention two main differences between F#/dotnet core and nodejs in SSR:
31
+
32
+
* F# is a compiled language, which means it's generally considered faster then a dynamic language, like js.
33
+
* Nodejs's single thread, event-driven, non-blocking I/O model works well in most web sites, but it is not good at CPU intensive tasks, including html rendering. Usually we need to run multi nodejs instances to take the advantage of multi-core systems. DotNet support non-blocking I/O (and `async/await` sugar), too. But the awesome part is that it also has pretty good support for multi-thread programming.
34
+
35
+
In a simple test in my local macbook, rendering on dotnet core is about ~2x faster then nodejs (with ReactDOMServer.renderToString + NODE_ENV=production). You can find more detail in the bottom of this page.
36
+
37
+
In a word, with this approach, you can not only get a better performance then nodejs, but also don't need the complexity of running and maintaining nodejs instances on your server!
38
+
39
+
Here is a list of Fable.Helpers.React API that support server-side rendering:
40
+
41
+
* HTML/CSS/SVG DSL function/unions, like `div`, `input`, `Style`, `Display`, `svg`, etc.
42
+
* str/ofString/ofInt/ofFloat
43
+
* ofOption/ofArray/ofList
44
+
* fragment
45
+
* ofType
46
+
* ofFunction
47
+
48
+
These don't support, but you can wrap it by `Fable.Helpers.Isomorphic.isomorphicView` to skip or render a placeholder on the server:
49
+
50
+
* ofImport
4
51
5
52
## Step 1: Reorganize your source files
6
53
7
54
Separate all your elmish view and types to standalone files, like this:
8
55
9
-
```F#
10
56
pages
11
57
|-- Home
12
58
|-- View.fs // contains view function.
@@ -19,7 +65,7 @@ View.fs and Types.fs will be shared between client and server.
19
65
20
66
## Step 2. Make sure shared files can be executed on the server side
21
67
22
-
Some code that works in Fable might throw a run time exception when executed on dotnet, so we should be careful with unsafe type casting and add compiler directives to remove some code if necessary.
68
+
Some code that works in Fable might throw a runtime exception on dotnet core, we should be careful with unsafe type casting and add compiler directives to remove some code if necessary.
23
69
24
70
Here are some hints about doing this:
25
71
@@ -40,9 +86,9 @@ Here are some hints about doing this:
40
86
41
87
### 2. Make sure your browser/js code won't be executed on the server side
42
88
43
-
One big challenge of sharing code between client and server is that server side has different API environment than client side. In this respect Fable + dotnet's SSR is not much different than nodejs, except in dotnet you should not only prevent browser's API call, but also js.
89
+
One big challenge of sharing code between client and server is that the server side has different API environment with client side. In this respect Fable + dotnet core's SSR is not much different than nodejs, except on dotnet core you should not only prevent browser's API call, but also js.
44
90
45
-
Thanks for Fable Compiler's `FABLE_COMPILER` directive, we can easly distinguish client environment and server environment and execute different code in each environment:
91
+
Thanks for Fable Compiler's `FABLE_COMPILER` directive, we can easily distinguish it's running on client or server and execute different code in different environment:
46
92
47
93
```#F
48
94
#if FABLE_COMPILER
@@ -52,7 +98,7 @@ Thanks for Fable Compiler's `FABLE_COMPILER` directive, we can easly distinguish
52
98
#endif
53
99
```
54
100
55
-
We also provice a help function in `Fable.Helpers.Isomorphic` of this, the definition is:
101
+
We also provide a help function in `Fable.Helpers.Isomorphic`, the definition is:
56
102
57
103
```F#
58
104
let inline isomorphicExec clientFn serverFn input =
@@ -71,7 +117,7 @@ open Fable.Import.JS
71
117
open Fable.Helpers.Isomorphic
72
118
open Fable.Import.Browser
73
119
74
-
// example code to make your document's title has marquee effect
120
+
// example code to add marquee effect to your document's title
### 3. Add a placeholder for components that cannot been rendered on the server side, like js native components.
93
139
94
-
In `Fable.Helpers.Isomorphic` we also implemented a help function to render a placeholder element for components that cannot been rendered on the server side, this function will also help [React.hydrate](https://reactjs.org/docs/react-dom.html#hydrate) to understand the differences between htmls rendered by client and server, so React won't treat it as a mistake and warn about it.
140
+
In `Fable.Helpers.Isomorphic` we also implemented a help function (`isomorphicView`) to render a placeholder element for components that cannot be rendered on the server side, this function will also help [React.hydrate](https://reactjs.org/docs/react-dom.html#hydrate) to understand the differences between htmls rendered by client and server, so React won't treat it as a mistake and warn about it.
95
141
96
142
```diff
97
143
open Fable.Core
@@ -116,9 +162,9 @@ let jsComp (props: JsCompProps) =
116
162
+isomorphicView jsComp jsCompServer { text="I'm rendered by a js Component!" }
117
163
```
118
164
119
-
## Step 3. Create your init state on the server side.
165
+
## Step 3. Create your initial state on the server side.
120
166
121
-
On the server side, you could create routes like normal MVC app, just make sure the model passed to server rendering function is exactly match the model on the client side in current route.
167
+
On the server side, you can create routes like normal MVC app, just make sure the model passed to server-side rendering function is exactly match the model on the client side in current route.
122
168
123
169
Here is an example:
124
170
@@ -165,8 +211,8 @@ let renderHtml () =
165
211
166
212
## Step 4. Update your elmish app's init function
167
213
168
-
1.Init your elmish app by state printed in the HTML.
169
-
2. Remove init commands that still fetch data which already printed in the HTML.
214
+
1.Initialize your elmish app by state printed in the HTML.
215
+
2. Remove initial commands that fetch state which already included in the HTML.
0 commit comments