SSR 服务端渲染

SSR
Server-side rendering
SSG
Static-site generation
CSR
Client-side rendering
ISR
Incremental Static Regeneration

View template / Server-side rendering

Python SSR?
Django Flask

React CSR


      const React = require('react');
      const {createRoot} = require('react-dom/client');

      const App = () => {
        const [value, setValue] = React.useState(0);
        return (
          <button onClick={() => setValue(value + 1)}>
            👍<sup>{value}</sup>
          </button>
        );
      };

      createRoot(document.getElementById("app")).render(<App />);
    
    

ReactDOM .createRoot

React template

const App = () => {
  const [value, setValue] = React.useState(0);
  return (
    <button style={{ fontSize: 96 }} onClick={() => setValue(value + 1)}>
      👍<sup>{value}</sup>
    </button>
  );
};

new Koa()
  .use(async (ctx) => {
    ctx.body = `
      <div id="app">${ReactDOMServer.renderToString(<App />)}</div>
    `;
  })
  .listen();

React DOM Hydrate


            const App = () => {
              const [value, setValue] = React.useState(0);
              return <button style={{fontSize: 96}} onClick={() => setValue(value + 1)}>👍<sup>{value}</sup></button>;
            };

            new Koa()
              .use(async ctx => {
                ctx.body = `
                  <script src="https://unpkg.com/react/umd/react.production.min.js"></script>
                  <script src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
                  <div id="app">${ReactDOMServer.renderToString(<App />)}</div>
                `;
              })
              .listen();
        
      

ReactDOM .createRoot .hydrateRoot

React SSR

const App = () => {
  const [value, setValue] = React.useState(0);
  return React.createElement(
    'button',
    { style: { fontSize: 96 }, onClick: () => setValue(value + 1) },
    '👍',
    React.createElement('sup', null, value)
  );
};

new Koa()
  .use(async (ctx) => {
    ctx.body = `
      <div id="app">${ReactDOMServer.renderToString(<App />)}</div>
      <script type="module">
        import React from "https://esm.sh/react";
        import { hydrateRoot } from "https://esm.sh/react-dom/client";
        hydrateRoot(document.getElementById("app"), React.createElement(${App}));
      </script>
    `;
  })
  .listen();

SSR vs CSR

Pros

  • 内容立即可见
    用户第一时间有内容可看, 不用对着空白页面等.
  • 数据获取
    后端提前准备好数据, 不必等待前端 js 运行后再调用 API.
  • 搜索引擎优化(SEO)
    搜索引擎有东西可爬.

Cons

  • 页面加载慢
    应用服务器比静态服务器慢.
  • 页面跳转慢
    前端路由跳转是瞬间的, 经过后端必然变慢.
  • 占用服务器资源
    nginx 能做的事, 现在需要 app server.
  • 易受攻击
    多了一个应用服务器, 多一层风险.
  • 开发复杂
    SSR 概念产生较晚, 社区生态没有完全做好准备.

SSR 框架(们)

Next.js

nextjs.org
  • next
  • swr
  • now
  • ncc
  • pkg

getServerSideProps


        import React from 'react';

        export async function getServerSideProps() {
          return {
            props: { votes: 10 },
          }
        }

        export default (props) => {
          const [value, setValue] = React.useState(props.votes);
          return (
            <button style={{fontSize: 96}} onClick={() => setValue(value + 1)}>
              👍<sup>{value}</sup>
            </button>
          );
        };
      
      

File-system based router


        /*
          pages
          ├── index.js               /
          └── blog
              ├── index.js           /blog
              └── [id].js            /blog/123
        */

        import Link from 'next/link';

        <Link href="/blog/hello-world">
      
      

API Routes


        /*
          pages
          └── api
              └── hello
                  └── [name].js            /api/hello/colder
        */

        export default function handler(req, res) {
          const { name } = req.query
          res.end('Hello ' + name);
        }
      
      

Other features

  • Built-In CSS Support
  • Image Component
  • Font Optimization
  • Environment Variables
    DB_PASSWORD
    NEXT_PUBLIC_ANALYTICS_ID

Astro

astro.build

pages/index.astro


        ---
        import Button from './Button.astro';
        import pokemon from '../data/pokemon.json';
        import Home from '../components/Home.jsx';
        ---

        <div>
          <Button title="Button 1" />

          <ul>
            {pokemon.map(({ name }) => <li>{name}</li>)}
          </ul>

          <!-- 100% HTML, Zero JavaScript loaded on the page! -->
          <Home />
        </div>

        <script is:inline>
          grecaptcha.ready(() => {
            grecaptcha.execute({RECAPTCHA_KEY});
          });
        </script>
      
      

Islands Architecture


        ---
        import Header from '../components/Header.jsx';
        import Sidebar from '../components/Sidebar.jsx';
        import Carousel from '../components/Carousel.vue';
        import Footer from '../components/Footer.svelte';
        ---

        <div>
          <Header client:load />
          <Sidebar client:load />
          <Carousel client:only="vue" />
          <Footer client:visible />
        </div>
      
      

fetch() in Astro


        ---
        import Profile from '../components/Profile.jsx';

        const res = await fetch('https://randomuser.me/api/');
        const data = await res.json();
        const user = data.results[0];
        ---

        <div>
          <h2>{user.name.first} {user.name.last}</h2>
          <Profile client:load user={user} />
        </div>
      
      

fetch() in React Component


        const res = await fetch('https://randomuser.me/api/');
        const data = await res.json();

        const user = data.results[0];
        console.log(user);

        export const Profile = () => {
          return (
            <h2>{user.name.first} {user.name.last}</h2>
          );
        };
      
      

File-system based router


        /*
          pages
          ├── index.astro            /
          └── blog
              ├── index.astro        /blog
              └── [id].astro         /blog/123
        */

        <a href="/blog/hello-world">
      
      

Endpoints


        /*
          pages
          ├── data.js                /data
          └── favicon.png.ts         /favicon.png
        */

        export async function get({params, request}) {
          return {
            body: JSON.stringify({
              name: 'Astro',
              url: 'https://astro.build/',
            }),
          };
        }
      
      

Qwik

qwik.builder.io

服务端渲染 html + js(1kb)

Serialization


        import { component$ } from '@builder.io/qwik';

        export const data = Promise.resolve('data');

        export const Demo = component$(() => {
          console.log(topLevel);

          return (
            <button onClick$={() => {
              console.log(topLevel);
            }} />
          );
        });
      
      

Edge Compute

Thank you