You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将SSR(服务端渲染)与PWA(渐进式Web应用)结合实现?

How to Combine SSR (Server Side Rendering) with PWA (Progressive Web App)

Great question! Combining SSR and PWA might seem contradictory at first—after all, SSR serves fully rendered HTML with data on the first load, while PWAs rely on caching a static app shell and loading data later. But the trick is to split your app into distinct layers and handle their caching separately. Here's how to make it work smoothly:

Core Concept: Split Static Shell vs. Dynamic Content

First, let's clarify the two pieces we need to isolate:

  • App Shell: The static, reusable parts of your app (navigation bar, footer, base styles, core interactivity JS) that rarely change. This is what PWAs cache for offline use.
  • Dynamic Content: The page-specific data-rendered HTML (product listings, user profiles, blog posts) that SSR generates on the first load, and which we'll fetch via API for subsequent visits.

Step-by-Step Implementation

1. Architect Your App with Separation in Mind

In your SSR template (whether using React, Vue, or a vanilla setup), explicitly split the shell and dynamic content blocks. For example:

<!-- Static App Shell (cached by PWA) -->
<header class="app-header">
  <nav>...</nav>
</header>

<!-- Dynamic Content (SSR-rendered first load, API-fetched later) -->
<main id="dynamic-content">
  <!-- Server-rendered data goes here (e.g., <article> for a blog post) -->
</main>

<!-- Static App Shell -->
<footer class="app-footer">...</footer>

2. Cache the App Shell via Service Worker

Use a service worker to cache only the static shell assets (not the full data-rendered HTML). Use a CacheFirst strategy here since the shell doesn't change often:

// Service Worker snippet
const SHELL_CACHE = 'app-shell-v1';
const SHELL_ASSETS = [
  '/', // Base HTML skeleton (without dynamic content)
  '/css/shell-styles.css',
  '/js/shell-interactivity.js',
  '/images/logo.png'
];

// Install: Cache the app shell
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(SHELL_CACHE)
      .then(cache => cache.addAll(SHELL_ASSETS))
  );
});

// Fetch: Serve shell from cache, handle dynamic data separately
self.addEventListener('fetch', (event) => {
  // Handle API requests (dynamic data)
  if (event.request.url.includes('/api/')) {
    event.respondWith(
      caches.open('api-cache-v1')
        .then(cache => {
          // Try network first, fall back to cached API data if offline
          return fetch(event.request)
            .then(networkRes => {
              cache.put(event.request, networkRes.clone());
              return networkRes;
            })
            .catch(() => cache.match(event.request));
        })
    );
  }
  // Handle app shell assets
  else if (SHELL_ASSETS.includes(event.request.url)) {
    event.respondWith(caches.match(event.request));
  }
});

3. Serve Full SSR HTML on First Load

When a user visits your app for the first time (or refreshes), your server renders the complete HTML—app shell + dynamic data injected into the #dynamic-content block. This ensures:

  • Crawlers see the full content for SEO
  • Users with JS disabled get a usable static page
  • The service worker installs in the background, caching the shell for future use

4. Hydrate & Use PWA for Subsequent Navigations

After the first load:

  1. Your client-side JS hydrates the SSR-rendered HTML into interactive components.
  2. For future page navigations, instead of requesting a full SSR page:
    • Load the cached app shell instantly
    • Fetch the required data via your API (using the cached API response if offline)
    • Render the new dynamic content into the #dynamic-content block

This gives you the speed of PWA navigation while retaining SSR's initial load benefits.

5. Manage Cache Invalidation

  • App Shell Updates: When you change the shell, increment the cache name (e.g., app-shell-v2). The service worker will install the new version, cache the updated shell, and activate it once users close all tabs of your app.
  • API Data Updates: Use strategies like StaleWhileRevalidate for API data—serve cached data immediately, then update the cache in the background with fresh data. Or add cache control headers to your API responses to set expiration times.

Key Takeaways

  • Separation is non-negotiable: Keep static shell and dynamic data strictly separated to avoid caching conflicts.
  • Dual caching strategies: Use CacheFirst for the shell, NetworkFirst or StaleWhileRevalidate for dynamic data.
  • SSR for first load, PWA for repeat visits: Get the best of both worlds—SEO-friendly initial load and fast, offline-capable subsequent navigations.

内容的提问来源于stack exchange,提问作者Amanuel Nega

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:12:43