阿里云主机折上折
  • 微信号
Current Site:Index > Server-side rendering

Server-side rendering

Author:Chuan Chen 阅读数:60417人阅读 分类: TypeScript

Basic Concepts of Server-Side Rendering

Server-Side Rendering (SSR) refers to the technique of completing page rendering work on the server side and sending the rendered HTML to the client. Compared to traditional Client-Side Rendering (CSR), SSR can present the initial page content faster, is more SEO-friendly, and provides a better first-screen loading experience.

// A simple Node.js server-side rendering example
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';

const app = express();

app.get('/', (req, res) => {
  const html = renderToString(<div>Hello SSR!</div>);
  res.send(`
    <!DOCTYPE html>
    <html>
      <head><title>SSR Example</title></head>
      <body>${html}</body>
    </html>
  `);
});

app.listen(3000);

Advantages of TypeScript in SSR

TypeScript brings type safety and a better development experience to server-side rendering applications. In SSR environments, frontend and backend code often need to share type definitions, and TypeScript's type system ensures data consistency between both ends.

// Example of shared type definitions
interface User {
  id: number;
  name: string;
  email: string;
}

// Server API returns User data
app.get('/api/user', (req, res) => {
  const user: User = {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com'
  };
  res.json(user);
});

// Client component uses the same User type
function UserProfile({ user }: { user: User }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

Common Frameworks for Implementing SSR

Modern frontend frameworks mostly provide SSR support, and combining them with TypeScript allows for building type-safe isomorphic applications.

SSR Implementation in Next.js

import { GetServerSideProps } from 'next';

interface PageProps {
  data: string;
  timestamp: number;
}

export const getServerSideProps: GetServerSideProps<PageProps> = async () => {
  return {
    props: {
      data: 'Server-side rendered data',
      timestamp: Date.now()
    }
  };
};

const SSRPage = ({ data, timestamp }: PageProps) => {
  return (
    <div>
      <h1>{data}</h1>
      <p>Rendered at: {new Date(timestamp).toLocaleString()}</p>
    </div>
  );
};

export default SSRPage;

SSR Configuration in Nuxt.js

// nuxt.config.ts
export default defineNuxtConfig({
  ssr: true,
  typescript: {
    strict: true
  }
});

// Using asyncData in page components
<script lang="ts">
export default defineComponent({
  async asyncData({ $http }) {
    const posts = await $http.$get('/api/posts');
    return { posts };
  },
  data() {
    return {
      posts: [] as Post[]
    };
  }
});
</script>

Data Fetching Strategies in SSR

Data fetching in server-side rendering requires special attention to ensure data is fetched on the server while avoiding duplicate fetching on the client.

Server-Side Data Injection

// Using window.__INITIAL_STATE__ to pass data
app.get('/', (req, res) => {
  const initialState = { user: { name: 'Alice' } };
  const html = renderToString(<App />);
  
  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <script>
          window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
        </script>
      </head>
      <body>
        <div id="root">${html}</div>
      </body>
    </html>
  `);
});

// Using injected data during client initialization
const root = hydrateRoot(
  document.getElementById('root'),
  <App initialState={window.__INITIAL_STATE__} />
);

Using SWR or React Query for Client-Side Data Supplementation

import { useQuery } from 'react-query';

function UserProfile() {
  // Fetch initial data during server-side rendering
  // The client will automatically update the data
  const { data } = useQuery('user', () => 
    fetch('/api/user').then(res => res.json())
  );
  
  return <div>{data?.name}</div>;
}

Performance Optimization in SSR

While server-side rendering can improve first-screen performance, it may also increase server load, requiring careful optimization.

Combining Static Generation with SSR

// Example of ISR (Incremental Static Regeneration) in Next.js
export async function getStaticProps() {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  return {
    props: { data },
    // Regenerate the page every 10 seconds
    revalidate: 10,
  };
}

Streaming SSR

// Example of streaming SSR in React 18
import { renderToPipeableStream } from 'react-dom/server';

app.get('/', (req, res) => {
  const { pipe } = renderToPipeableStream(<App />, {
    bootstrapScripts: ['/main.js'],
    onShellReady() {
      res.setHeader('Content-type', 'text/html');
      pipe(res);
    }
  });
});

Component-Level Caching

// Using react-ssr-prepass for component-level data prefetching
import { prepass } from 'react-ssr-prepass';

app.get('/', async (req, res) => {
  const element = <App />;
  await prepass(element); // Prefetch all component data
  
  const html = renderToString(element);
  res.send(html);
});

Common Issues and Solutions in SSR

Client-Server Rendering Mismatch

// Solution: Ensure consistent rendering on both ends
function MyComponent() {
  // Use useEffect to ensure execution only on the client
  const [mounted, setMounted] = useState(false);
  
  useEffect(() => {
    setMounted(true);
  }, []);

  return <div>{mounted ? 'Client' : 'Server'}</div>;
}

Global Object Access Issues

// Safely accessing the window object
function useWindow() {
  const [windowObj, setWindowObj] = useState<Window | null>(null);
  
  useEffect(() => {
    setWindowObj(window);
  }, []);

  return windowObj;
}

function MyComponent() {
  const window = useWindow();
  const width = window?.innerWidth || 0;
  
  return <div>Width: {width}</div>;
}

Style Handling

// Using CSS-in-JS libraries to handle styles in SSR
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';

app.get('/', (req, res) => {
  const sheet = new ServerStyleSheet();
  
  try {
    const html = renderToString(
      <StyleSheetManager sheet={sheet.instance}>
        <App />
      </StyleSheetManager>
    );
    
    const styleTags = sheet.getStyleTags();
    res.send(`<html><head>${styleTags}</head><body>${html}</body></html>`);
  } finally {
    sheet.seal();
  }
});

Advanced SSR Patterns

SSR in Micro-Frontends

// Implementing SSR micro-frontends using Module Federation
// webpack.config.js
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js'
      }
    })
  ]
};

// Dynamically loading remote components during SSR
const RemoteComponent = dynamic(
  () => import('remoteApp/Component'),
  { ssr: true }
);

SSR in Edge Computing

// Implementing edge SSR using Cloudflare Workers
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request: Request) {
  const html = renderToString(<App />);
  
  return new Response(html, {
    headers: { 'Content-Type': 'text/html' }
  });
}

Progressive SSR

// Deciding whether to use SSR based on device capability
function shouldSSR(req: Request) {
  const userAgent = req.headers.get('user-agent') || '';
  const isSlowDevice = /(Android|iPhone|Mobile)/i.test(userAgent);
  return !isSlowDevice; // Skip SSR for slow devices
}

app.get('/', (req, res) => {
  if (shouldSSR(req)) {
    const html = renderToString(<App />);
    res.send(html);
  } else {
    res.sendFile('index.csr.html');
  }
});

Testing and Monitoring

SSR Unit Testing

// Testing SSR components with Jest
import { renderToString } from 'react-dom/server';

describe('SSR', () => {
  it('should render without errors', () => {
    const html = renderToString(<App />);
    expect(html).toContain('<div>');
  });
});

Performance Monitoring

// Monitoring SSR performance using the Performance API
app.use((req, res, next) => {
  const start = performance.now();
  
  res.on('finish', () => {
    const duration = performance.now() - start;
    console.log(`SSR took ${duration}ms for ${req.url}`);
  });
  
  next();
});

Error Tracking

// Catching errors during SSR
app.get('/', async (req, res) => {
  try {
    const html = await renderToStringWithCatch(<App />);
    res.send(html);
  } catch (error) {
    console.error('SSR error:', error);
    res.status(500).send('Server error');
  }
});

async function renderToStringWithCatch(element: ReactElement) {
  try {
    return renderToString(element);
  } catch (error) {
    // Log error information
    Sentry.captureException(error);
    // Return fallback UI
    return renderToString(<FallbackUI />);
  }
}

Modern SSR Architecture Patterns

Islands Architecture

// Simple example of implementing Islands architecture
function Island({ id, component: Component }: { id: string; component: ComponentType }) {
  useEffect(() => {
    // Client hydration logic
    console.log(`Island ${id} hydrated`);
  }, []);
  
  return <Component />;
}

// During server-side rendering
const html = renderToString(
  <div>
    <StaticContent />
    <Island id="counter" component={Counter} />
  </div>
);

Partial Hydration

// Implementing partial hydration using React 18's lazy and Suspense
const InteractiveComponent = lazy(() => import('./InteractiveComponent'));

function App() {
  return (
    <div>
      <StaticContent />
      <Suspense fallback={<div>Loading...</div>}>
        <InteractiveComponent />
      </Suspense>
    </div>
  );
}

Server Components

// Example of React Server Components
// ServerComponent.server.js
export default function ServerComponent() {
  const data = fetchDataOnServer(); // Executes only on the server
  
  return (
    <div>
      <h1>{data.title}</h1>
      <ClientComponent />
    </div>
  );
}

// ClientComponent.client.js
'use client';
export default function ClientComponent() {
  const [state, setState] = useState(0); // Client-side interactivity
  
  return <button onClick={() => setState(c => c + 1)}>Count: {state}</button>;
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.