client-side-rendering
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseClient-side Rendering
客户端渲染(CSR)
In Client-Side Rendering (CSR) only the barebones HTML container for a page is rendered by the server. The logic, data fetching, templating and routing required to display content on the page is handled by JavaScript code that executes in the browser/client. CSR became popular as a method of building single-page applications. It helped to blur the difference between websites and installed applications.
在客户端渲染(CSR)中,服务器仅渲染页面的基础HTML容器。页面上显示内容所需的逻辑、数据获取、模板和路由均由在浏览器/客户端中执行的JavaScript代码处理。CSR作为构建单页应用(SPA)的方法流行起来,它模糊了网站和已安装应用之间的界限。
When to Use
适用场景
- Use this for internal tools, dashboards, or SPAs where SEO is not a priority
- This is helpful when you need a fully interactive single-page application experience
- 适用于SEO优先级不高的内部工具、仪表板或SPA
- 当你需要完全交互式的单页应用体验时,这种方式很有帮助
Instructions
实施建议
- Keep initial JavaScript bundles small (< 100-170KB minified/gzipped) for fast First Contentful Paint
- Use code-splitting and lazy loading to defer non-critical JavaScript
- Consider SSR/SSG for public-facing pages where SEO and initial load performance matter
- Use service workers and application shell caching for offline and repeat visit performance
- 保持初始JavaScript包体积较小(压缩/ gzip后小于100-170KB),以实现快速的首次内容绘制(FCP)
- 使用代码分割和懒加载来延迟加载非关键JavaScript
- 对于SEO和初始加载性能重要的面向公众页面,考虑使用SSR/SSG
- 使用Service Worker和应用外壳缓存来优化离线访问和重复访问的性能
Details
详细说明
Basic structure
基本结构
Consider this simple example for showing and updating the current time on a page using React.
tsx
import { createRoot } from 'react-dom/client'
import { useEffect, useState } from 'react'
function Clock() {
const [time, setTime] = useState(new Date())
useEffect(() => {
const id = setInterval(() => setTime(new Date()), 1000)
return () => clearInterval(id)
}, [])
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {time.toLocaleTimeString()}.</h2>
</div>
)
}
createRoot(document.getElementById('root')!).render(<Clock />)html
<div id="root"></div>The HTML consists of just a single root tag. Content display and updates on the other hand are handled completely in JavaScript. There is no round trip to the server and rendered HTML is updated in-place. Here time could be replaced by any other real-time information like exchange rates or stock prices obtained from an API and displayed without refreshing the page or a round trip to the server.
<div>考虑这个使用React在页面上显示并更新当前时间的简单示例。
tsx
import { createRoot } from 'react-dom/client'
import { useEffect, useState } from 'react'
function Clock() {
const [time, setTime] = useState(new Date())
useEffect(() => {
const id = setInterval(() => setTime(new Date()), 1000)
return () => clearInterval(id)
}, [])
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {time.toLocaleTimeString()}.</h2>
</div>
)
}
createRoot(document.getElementById('root')!).render(<Clock />)html
<div id="root"></div>HTML仅包含一个根标签。而内容的显示和更新完全由JavaScript处理,无需与服务器进行往返通信,渲染后的HTML会就地更新。这里的时间可以替换为从API获取的任何其他实时信息,如汇率或股票价格,无需刷新页面或与服务器通信即可显示。
<div>JavaScript bundles and Performance
JavaScript包与性能
As the complexity of the page increases to show images, display data from a data store and include event handling, the complexity and size of the JavaScript code required to render the page will also increase. CSR resulted in large JavaScript bundles which increased the FCP and TTI of the page.
As the size of bundle.js increases, the FCP and TTI are pushed forward. This implies that the user will see a blank screen for the entire duration between FP and FCP.
随着页面复杂度的增加(如显示图片、展示数据存储中的数据、添加事件处理),渲染页面所需的JavaScript代码的复杂度和体积也会增加。CSR会导致JavaScript包过大,从而延长页面的首次内容绘制(FCP)和交互时间(TTI)。
当bundle.js的体积增大时,FCP和TTI会被推迟,这意味着用户在首次绘制(FP)到首次内容绘制(FCP)的整个时间段内都会看到空白屏幕。
Pros and Cons
优缺点
With React most of the application logic is executed on the client and it interacts with the server through API calls to fetch or save data. Almost all of the UI is thus generated on the client. The entire web application is loaded on the first request. As the user navigates by clicking on links, no new request is generated to the server for rendering the pages. The code runs on the client to change the view/data.
CSR allows us to have a Single-Page Application that supports navigation without page refresh and provides a great user experience. As the data processed to change the view is limited, routing between pages is generally faster making the CSR application seem more responsive. CSR also allows developers to achieve a clear separation between client and server code.
Note (React 18+): Reevaluate Pure CSR for Initial LoadsWhile CSR yields a rich interactive experience after load, it has well-known drawbacks for first-page load performance and SEO. Today's best practice is to avoid pure-CSR for content-rich or public-facing pages. Instead, use hybrid approaches (SSR/SSG plus hydration) for the initial render. Frameworks like Next.js now default to pre-rendering pages on the server (or at build time) and then hydrating on the client.Server-rendering HTML can drastically improve FCP and make content indexable for search engines. React 18's improvements (automatic batching, Suspense, streaming) make SSR + hydration very performant. React 18 also introduced Progressive Hydration and Selective Hydration which mitigate the traditional TTI gap—React can hydrate parts of the UI as their scripts arrive or as the user interacts.Conclusion: Pure CSR (loading a big bundle and rendering everything on client) is generally discouraged for large apps. Use SSR/SSG for initial content and hydrate on the client. If you do use CSR (e.g., an internal dashboard where SEO doesn't matter), apply aggressive code-splitting and use React 18'swith lazy-loaded components to defer loading non-critical parts of the UI.<Suspense>
Despite the great interactive experience that it provides, there are a few pitfalls to CSR:
-
SEO considerations: Most web crawlers can interpret server rendered websites in a straight-forward manner. Things get slightly complicated in the case of client-side rendering as large payloads and a waterfall of network requests (e.g for API responses) may result in meaningful content not being rendered fast enough for a crawler to index it. Crawlers may understand JavaScript but there are limitations. As such, some workarounds are required to make a client-rendered website SEO friendly.
-
Performance: With client-side rendering, the response time during interactions is greatly improved as there is no round trip to the server. However, for browsers to render content on client-side the first time, they have to wait for the JavaScript to load first and start processing. Thus users will experience some lag before the initial page loads. This may affect the user experience as the size of JS bundles get bigger and/or the client does not have sufficient processing power.
-
Code Maintainability: Some elements of code may get repeated across client and server (APIs) in different languages. In other cases, clean separation of business logic may not be possible. Examples of this could include validations and formatting logic for currency and date fields.
-
Data Fetching: With client-side rendering, data fetching is usually event-driven. The page could initially be loaded without any data. Data may be subsequently fetched on the occurrence of events like page-load or button-clicks using API calls. Depending on the size of data this could add to the load/interaction time of the application.
The importance of these considerations may be different across applications. Developers are often interested in finding SEO friendly solutions that can serve pages faster without compromising on the interaction time.
使用React时,大多数应用逻辑在客户端执行,它通过API调用与服务器交互以获取或保存数据。几乎所有UI都在客户端生成。整个Web应用在首次请求时加载完成。当用户通过点击链接导航时,不会向服务器发送新的页面渲染请求,而是由客户端代码运行来更改视图/数据。
CSR支持构建无需页面刷新即可导航的单页应用(SPA),提供出色的用户体验。由于更改视图时处理的数据有限,页面间路由通常更快,使CSR应用看起来响应更迅速。CSR还允许开发者实现客户端与服务器代码的清晰分离。
注意(React 18+):重新评估纯CSR在初始加载中的应用虽然CSR在加载后能提供丰富的交互体验,但它在首屏加载性能和SEO方面存在众所周知的缺点。如今的最佳实践是避免在内容丰富或面向公众的页面中使用纯CSR。相反,应使用混合方法(SSR/SSG加注水)进行初始渲染。Next.js等框架现在默认在服务器(或构建时)预渲染页面,然后在客户端进行注水。服务器渲染HTML可以显著提升FCP,并使内容可被搜索引擎索引。React 18的改进(自动批处理、Suspense、流式传输)使SSR + 注水的性能非常出色。React 18还引入了渐进式注水和选择性注水,缓解了传统的TTI差距——React可以在脚本到达或用户交互时为UI的部分内容进行注水。结论: 纯CSR(加载大体积包并在客户端渲染所有内容)通常不推荐用于大型应用。对初始内容使用SSR/SSG,然后在客户端进行注水。如果确实要使用CSR(例如SEO无关紧要的内部仪表板),请采用激进的代码分割,并结合React 18的和懒加载组件来延迟加载UI的非关键部分。<Suspense>
尽管CSR提供了出色的交互体验,但它也存在一些缺陷:
-
SEO考量: 大多数网络爬虫可以直接解析服务器渲染的网站。而客户端渲染的情况则稍显复杂,因为大体积负载和网络请求瀑布流(如API响应)可能导致有意义的内容无法足够快地渲染,以供爬虫索引。爬虫可以理解JavaScript,但存在局限性。因此,需要一些变通方法来使客户端渲染的网站对SEO友好。
-
性能: 客户端渲染在交互过程中的响应时间大幅提升,因为无需与服务器往返通信。然而,浏览器首次在客户端渲染内容时,必须先等待JavaScript加载并开始处理。因此,用户在初始页面加载前会经历一些延迟。随着JS包体积增大或客户端处理能力不足,这可能会影响用户体验。
-
代码可维护性: 某些代码元素可能会在客户端和服务器(API)中以不同语言重复出现。在其他情况下,可能无法实现业务逻辑的清晰分离。例如,货币和日期字段的验证和格式化逻辑。
-
数据获取: 在客户端渲染中,数据获取通常是事件驱动的。页面最初可能没有加载任何数据,随后可能在页面加载或按钮点击等事件发生时通过API调用获取数据。根据数据大小,这可能会增加应用的加载/交互时间。
这些考量的重要性因应用而异。开发者通常希望找到对SEO友好的解决方案,能够在不影响交互时间的前提下更快地提供页面。
Improving CSR performance
提升CSR性能
Since performance for CSR is inversely proportional to the size of the JavaScript bundle, the best thing we can do is structure our JavaScript code for optimal performance. Following is a list of pointers that could help:
-
Budgeting JavaScript: Ensure that you have a reasonably tight JavaScript budget for your initial page loads. An initial bundle of < 100-170KB minified and gzipped is a good starting point. Code can then be loaded on-demand as features are needed.
-
Preloading: This technique can be used to preload critical resources that would be required by the page, earlier in the page lifecycle. Critical resources may include JavaScript which can be preloaded by including the following directive in thesection of the HTML.
<head>
html
<link rel="preload" as="script" href="critical.js" />This informs the browser to start loading the file before the page rendering mechanism starts. The script will thus be available earlier and will not block the page rendering mechanism thereby improving the performance.
critical.js-
Lazy loading: With lazy loading, you can identify resources that are non-critical and load these only when needed. Initial page load times can be improved using this approach as the size of resources loaded initially is reduced. For example, a chat widget component would generally not be needed immediately on page load and can be lazy loaded.
-
Code Splitting: To avoid a large bundle of JavaScript code, you could start splitting your bundles. Code-Splitting is supported by bundlers like Webpack where it can be used to create multiple bundles that can be dynamically loaded at runtime. Code splitting also enables you to lazy load JavaScript resources.
-
Application shell caching with service workers: This technique involves caching the application shell which is the minimal HTML, CSS, and JavaScript powering a user interface. Service workers can be used to cache the application shell offline. This can be useful in providing a native single-page app experience where the remaining content is loaded progressively as needed.
With these techniques, CSR can help to provide a faster Single-Page Application experience with a decent FCP and TTI.
由于CSR的性能与JavaScript包的体积成反比,我们能做的最佳优化就是构建性能最优的JavaScript代码结构。以下是一些有用的建议:
-
JavaScript预算: 确保初始页面加载的JavaScript预算合理。初始包压缩并gzip后小于100-170KB是一个不错的起点。然后可以根据功能需求按需加载代码。
-
预加载: 该技术可用于在页面生命周期的早期预加载页面所需的关键资源。关键资源可能包括JavaScript,可以通过在HTML的部分添加以下指令来预加载:
<head>
html
<link rel="preload" as="script" href="critical.js" />这会通知浏览器在页面渲染机制启动前开始加载文件。脚本将更早可用,不会阻塞页面渲染机制,从而提升性能。
critical.js-
懒加载: 通过懒加载,你可以识别非关键资源,仅在需要时加载它们。这种方法可以减少初始加载的资源体积,从而缩短初始页面加载时间。例如,聊天窗口组件通常不需要在页面加载时立即使用,可以进行懒加载。
-
代码分割: 为避免JavaScript代码包过大,你可以开始拆分包。Webpack等打包工具支持代码分割,可用于创建多个可在运行时动态加载的包。代码分割还能实现JavaScript资源的懒加载。
-
使用Service Worker缓存应用外壳: 该技术包括缓存应用外壳,即支持用户界面的最小HTML、CSS和JavaScript。Service Worker可用于离线缓存应用外壳,这有助于提供原生单页应用体验,剩余内容可根据需要逐步加载。
通过这些技术,CSR可以帮助提供更快的单页应用体验,同时保证良好的FCP和TTI。