Compared to other framework features, this framework additionally has the capability to degrade from Server-Side Rendering
to Client-Side Rendering
with one click.
Let’s see what the differences are between Server-Side Rendering
and Client-Side Rendering
.
Client-Side Rendering
, HTML
serves merely as a static skeleton. When the client makes a request, the server does no processing and returns the original file directly to the client. Then, based on the JavaScript
in the HTML
, DOM is generated and inserted into the HTML.
Server-Side Rendering (SSR)
: When the browser requests a page URL, the server assembles the HTML
text we need and returns it to the browser. After this HTML
text is parsed by the browser, it can directly build the desired DOM tree and display it on the page without executing JavaScript
scripts.
The most important difference between client-side rendering and server-side rendering is who completes the complete assembly of the HTML
file. If it’s completed on the server side and then returned to the client, it’s server-side rendering. If the client does more work to complete the HTML
assembly, then it’s client-side rendering.
Note: In different multi-rendering modes, the timing of data acquisition by this framework’s rendering methods is different. For details, see Data Fetching
It’s beneficial for crawlers to crawl your pages. When searching for related content on search engines, your web pages can rank higher, thus increasing your traffic.
Compared to client-side rendering, server-side rendering has already obtained an HTML text with data after the browser requests the URL. The browser only needs to parse the HTML and directly build the DOM tree. With client-side rendering, you need to first get an empty HTML page, at which point the page has already entered a white screen state. Then it needs to go through several processes including loading and executing JavaScript, requesting the backend server for data, and JavaScript rendering the page before the final page can be seen. Especially in complex applications, because JavaScript scripts need to be loaded, the more complex the application, the more and larger JavaScript scripts need to be loaded, which can cause the application’s first screen loading time to be very long, thereby reducing the user experience.
Since the server only needs to return the simplest page skeleton, the server pressure will be smaller and can handle more QPS
.
Writing a mature server-side rendering application undoubtedly requires higher mental and capability requirements from developers.
In the ssr
framework, we provide multiple solutions for degrading to client-side rendering.
The framework uses server-side rendering by default when started. If you want to enable render degradation, simply add the query
parameter ?csr=xxx
after the request URL
.
For example, the http://ssr-fc.com website has server-side rendering enabled by default. You can clearly feel that the page opens instantly with no white screen waiting time. After adding the parameter http://ssr-fc.com?csr=true, which is enabling client-side rendering and then opening the website, you can clearly feel there’s a certain amount of white screen time, specifically manifested as a page flickering process.
This solution is suitable for developers to test locally.
When publishing services, two modes are supported. The default is mode: 'ssr'
mode. You can also set csr
as the default rendering mode through mode: 'csr'
in Application Configuration.
Both ssr-core-react and ssr-core-vue (vue3) modules support this method.
Degrade to client-side rendering when the application execution encounters an error and catches
the error
. You can also decide to degrade to csr
mode at appropriate times based on specific business logic. You can also integrate with a publish-subscribe mechanism to set the current rendering mode in real-time through a publishing platform.
Below you can see an example of the ssr-core-react
module. If you have better approaches, welcome to give us feedback.
String degradation handling is simple. We only need to try catch
the error and then directly modify the rendering mode to get new results. Because at this time, component rendering is executed when the render
method is called.
import { render } from 'ssr-core'
try {
const htmlStr = await render(this.ctx)
return htmlStr
} catch (error) {
const htmlStr = await render(this.ctx, {
mode: 'csr'
})
return htmlStr
}
When the server
has problems, this disaster recovery approach is better. An even better approach is to configure disaster recovery at the gateway level and redirect requests to cdn
.
Stream return form degradation handling is slightly more complicated. In Nest.js
or express
-based frameworks, we can use the following approach for degradation.
Here we additionally divide into Vue3
and non-Vue3
cases.
In Vue3
’s renderToNodeStream
method, when rendering errors occur, errors are thrown synchronously. Developers can directly use try catch
at the upper level to capture them.
try {
const stream = await render<Readable>(ctx, {
stream: true
})
stream.pipe(res, { end: false })
stream.on('end', () => {
res.end()
})
} catch (error) {
const stream = await render<Readable>(ctx, {
stream: true,
mode: 'csr'
})
stream.pipe(res, { end: false })
stream.on('end', () => {
res.end()
})
}
In Vue2/React
, they trigger error
through stream.emit
at the underlying level. In this case, developers need to manually listen to events.
const stream = await render<Readable>(ctx, {
stream: true
})
stream.pipe(res, { end: false })
stream.on('error', async () => {
stream.destroy() // Destroy the old error stream
const newStream = await render<Readable>(ctx, {
stream: true,
mode: 'csr'
})
newStream.pipe(res, { end: false })
newStream.on('end', () => {
res.end()
})
})
stream.on('end', () => {
res.end()
})
In Midway.js/Koa
-based frameworks, use the following approach:
const stream = await render<Readable>(this.ctx, {
stream: true,
mode: 'ssr'
})
stream.on('error', async () => {
stream.destroy()
const newStream = await render<string>(ctx, {
stream: false, // Here we can only use string form to render, koa cannot reassign stream to body
mode: 'csr'
})
this.ctx.res.end(newStream)
})
this.ctx.body = stream
The principle of implementing render degradation functionality in the ssr
framework is very simple. If you have already used this framework for project development, you should have noticed that we don’t have traditional index.html
files, nor any template engines. Our html
page layout is completely rendered through JSX
or Vue SFC
.
After reading about the differences between Server-Side Rendering
applications and Client-Side Rendering
applications above, we can see that after degrading to client-side rendering, we don’t need to render page components and data fetching on the server side. We only need to render an empty html
skeleton. In React
scenarios, this is represented by code as follows:
const layoutFetchData = (!isCsr && layoutFetch) ? await layoutFetch(ctx) : null
const fetchData = (!isCsr && routeItem.fetch) ? await routeItem.fetch(ctx) : null
return (
<Layout ctx={ctx} config={config} staticList={staticList} viteReactScript={viteReactScript}>
{isCsr ? <></> : <Component />}
</Layout>
)
The implementation principle can be seen very easily. Similarly, in Vue
scenarios, we implement similar functionality through slot
.
When errors occur, degradation is performed. We categorize errors into two types: Actual Runtime Errors
and Resource Loading Errors
.
For Actual Runtime Errors
, these commonly occur during specific lifecycle execution or when components render
. For this type of error, we can avoid them through the degradation capabilities provided by the framework.
For Resource Loading Errors
, which are code we write at the top level of files, for example, if we bind browser objects in files import
ed at the component top level, then regardless of which rendering mode we use, this type of error will appear. Because when we initialize the route structure, we execute the component import
logic once. For this type of error, we can only manually write code to be compatible and solve it. Reference documentation.
In this framework, render degradation in most scenarios is for specific page components of the pages
type. For layout
components and App
components, rendering will be performed regardless of which rendering mode is used. Therefore, we recommend not writing business logic-related code in these two types of components, and try to store no logic
type code.
Rendering html
tags through frontend frameworks is not recommended by these frameworks. We can see that code uses approaches like dangerouslySetInnerHTML
to remind us not to do this. Because it’s very easy to be injected with malicious scripts leading to xss. So we must strictly control this part of rendered content and absolutely cannot have parts that users can control.
When injecting page data, we use serialize-javascript to serialize window.__INITIAL_DATA__
. However, for other content injection in the html
head, especially script
tag-related content, developers need to pay close attention.
This feature is rarely used. It may only be needed unless the developer’s deployment environment doesn’t have Node.js services or needs disaster recovery CDN degradation for core applications.
With this feature, we will generate an html
file for direct deployment. The effect is the same as traditional SPA
applications. If developers intend to deploy in html
form from the beginning, we don’t recommend using this framework. Please use scaffolding like vue-cli
, create-react-app
, etc.
Requires version >=5.5.62
$ npx ssr build --html
After building, we will generate a build/index.html
file that can be directly used for deployment. But choosing this solution means: