Server-side rendering
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
下一篇:与微前端架构