この記事はバージョン Spring ’26 において執筆しています。
現在の動作と異なる場合がありますので、ご認識おきください。
前回の記事では、Spring ’26で登場した Experience Delivery のアーキテクチャ概要と、導入判断の基準について解説しました。
今回はその続編として、「実際にどう実装すれば、理論値であるロード時間60%短縮を実現できるのか?」 というエンジニアリングの深層に踏み込みます。単に機能を有効化するだけでは、Core Web Vitalsのスコアは劇的には改善しません。Node.jsランタイムの特性を理解し、適切なツールとコードでチューニングを施す必要があります。
本記事では、LCP(Largest Contentful Paint)改善を中心とした実装テクニックと、品質担保のためのワークフローを網羅的に解説します。
定量目標:なぜここまでやるのか
Experience Deliveryを適切に実装したLWRサイトは、従来のCSR(クライアントサイドレンダリング)と比較して以下のパフォーマンス向上を示しています。
- ページロード時間: 最大 60%短縮
- LCP (Largest Contentful Paint): サブ秒(1秒未満)での表示が可能
- CLS (Cumulative Layout Shift): サーバー生成HTMLによるレイアウト安定化でほぼゼロに
この数値は「勝手に速くなる」ものではなく、以下の技術的要件を満たしたときに初めて達成されます。
開発ワークフローの刷新:lwr audit の導入
SSR開発において最も恐れるべきは、「デプロイしてみたらサーバーでクラッシュした」 という事態です。DOM API(window, document)への不適切なアクセスは、Node.js環境では致命的なエラーとなります。
これを防ぐため、Spring ’26からは開発サイクルに lwr audit コマンドを組み込むことが推奨されます。
静的解析によるガードレール
ローカル開発環境で lwr audit コマンドを実行し、デプロイ前にコードを監査します。LWR CLIはSalesforce CLI (sf) とは独立したツールですが、npx を使用することでインストール不要で即座に実行可能です。
以下のコマンドをプロジェクトルートで実行し、LWCコンポーネントツリー全体をスキャンします。
# プロジェクト内のコンポーネントを監査:ディレクトリのみを対象とする
ls -F force-app/main/default/lwc | grep "/$" | sed 's/\/$//' | xargs npx lwr audit --components
検出される主なエラー:
connectedCallback等、サーバー実行されるライフサイクルフック内でのwindow/documentへのアクセス- Node.js環境でサポートされていないAPIの使用
- ハイドレーションの不整合(サーバーとクライアントでのマークアップ不一致)
この監査プロセスをCI/CDパイプライン(GitHub Actionsなど)に組み込み、「Auditが通らなければデプロイさせない」 という運用を徹底することが、品質担保の第一歩です。
参考までに、私の環境でコマンドを実行した時の結果と解説をします。
コマンドの結果と解説を展開する
PASS (成功): 対象となった全コンポーネントにおいて、静的解析上の問題は見つかりませんでした。
WARN (警告): 大量に出ている警告は、すべて 「標準コンポーネント/モジュール」 に対するものです。lightning/navigation, lightning/messageService など。
ツールはローカル環境(PC)で実行されていますが、これらの標準モジュールのソースコードはSalesforceサーバー上にしかないため、「中身が確認できないから、念のため人間がチェックしてね」という意図で警告を出しています。これらはSalesforce上で正しく機能するため、基本的に無視して問題ありません。ただし、connectedCallback() などで自動的に呼び出すロジックになっている場合はSSR時にサーバー上で実行できずにエラーになる可能性があるモジュールも存在するため注意は必要です。
eiji@MacBook-Air-AG Antigravity % ls -F force-app/main/default/lwc | grep "/$" | sed 's/\/$//' | xargs npx lwr audit --components
✨ Auditing SSR capabilities for c/abTestHeroBanner, c/abTestHeroBannerCms, c/agentScheduleCalendar, c/agentScheduleDashboard, c/campaignBanner, c/campaignBannerEptb, c/cmsImageDisplay, c/customNotificationBanner, c/darkModeConfigurator, c/darkModeSwitchButton, c/darkModeSwitcherUltimate, c/darkModeToggle, c/homeAgentHero, c/listingAvailabilityCalendar, c/listingDetailReservation, c/listingPhotoSlider, c/loginAndLogoutButton, c/logoutButton, c/myCustomDatatable, c/opacitySliderEditor, c/productFamilySelector, c/propertySearchContainer, c/propertySearchFilter, c/propertySearchResult, c/recentAccounts, c/recordLink, c/recordListGeneric, c/removableColorPicker, c/royalButler, c/royalCartIcon, c/royalProductDetail, c/scrollToTopButton, c/skeletonScreenExample, c/tourReservationModal...
PASS c/abTestHeroBanner contains no capability incompatibilities!
PASS c/abTestHeroBannerCms contains no capability incompatibilities!
WARN c/abTestHeroBannerCms depends on one or more components which are not available locally and must be analyzed manually:
- lightning/cmsDeliveryApi
PASS c/agentScheduleCalendar contains no capability incompatibilities!
WARN c/agentScheduleCalendar depends on one or more components which are not available locally and must be analyzed manually:
- lightning/icon
- lightning/buttonIcon
- lightning/navigation
PASS c/agentScheduleDashboard contains no capability incompatibilities!
WARN c/agentScheduleDashboard depends on one or more components which are not available locally and must be analyzed manually:
- lightning/icon
- lightning/buttonIcon
- lightning/formattedDateTime
- lightning/formattedPhone
- lightning/formattedEmail
- lightning/platformShowToastEvent
- lightning/navigation
- lightning/confirm
PASS c/campaignBanner contains no capability incompatibilities!
WARN c/campaignBanner depends on one or more components which are not available locally and must be analyzed manually:
- lightning/button
- lightning/navigation
PASS c/campaignBannerEptb contains no capability incompatibilities!
WARN c/campaignBannerEptb depends on one or more components which are not available locally and must be analyzed manually:
- lightning/button
- lightning/navigation
PASS c/cmsImageDisplay contains no capability incompatibilities!
WARN c/cmsImageDisplay depends on one or more components which are not available locally and must be analyzed manually:
- experience/cmsDeliveryApi
PASS c/customNotificationBanner contains no capability incompatibilities!
WARN c/customNotificationBanner depends on one or more components which are not available locally and must be analyzed manually:
- lightning/icon
PASS c/darkModeConfigurator contains no capability incompatibilities!
WARN c/darkModeConfigurator depends on one or more components which are not available locally and must be analyzed manually:
- lightning/buttonIcon
- lightning/accordionSection
- lightning/accordion
PASS c/darkModeSwitchButton contains no capability incompatibilities!
WARN c/darkModeSwitchButton depends on one or more components which are not available locally and must be analyzed manually:
- lightning/buttonIcon
PASS c/darkModeSwitcherUltimate contains no capability incompatibilities!
WARN c/darkModeSwitcherUltimate depends on one or more components which are not available locally and must be analyzed manually:
- lightning/buttonIcon
PASS c/darkModeToggle contains no capability incompatibilities!
WARN c/darkModeToggle depends on one or more components which are not available locally and must be analyzed manually:
- lightning/buttonIcon
PASS c/homeAgentHero contains no capability incompatibilities!
WARN c/homeAgentHero depends on one or more components which are not available locally and must be analyzed manually:
- lightning/icon
- lightning/formattedRichText
- lightning/spinner
PASS c/listingAvailabilityCalendar contains no capability incompatibilities!
WARN c/listingAvailabilityCalendar depends on one or more components which are not available locally and must be analyzed manually:
- lightning/icon
- lightning/buttonIcon
- lightning/alert
PASS c/listingDetailReservation contains no capability incompatibilities!
WARN c/listingDetailReservation depends on one or more components which are not available locally and must be analyzed manually:
- lightning/button
- lightning/alert
PASS c/listingPhotoSlider contains no capability incompatibilities!
WARN c/listingPhotoSlider depends on one or more components which are not available locally and must be analyzed manually:
- lightning/icon
PASS c/loginAndLogoutButton contains no capability incompatibilities!
PASS c/logoutButton contains no capability incompatibilities!
PASS c/myCustomDatatable contains no capability incompatibilities!
WARN c/myCustomDatatable depends on one or more components which are not available locally and must be analyzed manually:
- lightning/datatable
PASS c/opacitySliderEditor contains no capability incompatibilities!
WARN c/opacitySliderEditor depends on one or more components which are not available locally and must be analyzed manually:
- lightning/slider
PASS c/productFamilySelector contains no capability incompatibilities!
WARN c/productFamilySelector depends on one or more components which are not available locally and must be analyzed manually:
- lightning/combobox
PASS c/propertySearchContainer contains no capability incompatibilities!
WARN c/propertySearchContainer depends on one or more components which are not available locally and must be analyzed manually:
- lightning/spinner
PASS c/propertySearchFilter contains no capability incompatibilities!
PASS c/propertySearchResult contains no capability incompatibilities!
PASS c/recentAccounts contains no capability incompatibilities!
WARN c/recentAccounts depends on one or more components which are not available locally and must be analyzed manually:
- lightning/formattedDateTime
- lightning/card
PASS c/recordLink contains no capability incompatibilities!
WARN c/recordLink depends on one or more components which are not available locally and must be analyzed manually:
- lightning/navigation
PASS c/recordListGeneric contains no capability incompatibilities!
WARN c/recordListGeneric depends on one or more components which are not available locally and must be analyzed manually:
- lightning/datatable
- lightning/card
- lightning/uiRelatedListApi
PASS c/removableColorPicker contains no capability incompatibilities!
PASS c/royalButler contains no capability incompatibilities!
PASS c/royalCartIcon contains no capability incompatibilities!
WARN c/royalCartIcon depends on one or more components which are not available locally and must be analyzed manually:
- lightning/messageService
PASS c/royalProductDetail contains no capability incompatibilities!
WARN c/royalProductDetail depends on one or more components which are not available locally and must be analyzed manually:
- lightning/spinner
- lightning/uiRecordApi
- lightning/messageService
- lightning/navigation
PASS c/scrollToTopButton contains no capability incompatibilities!
PASS c/skeletonScreenExample contains no capability incompatibilities!
PASS c/tourReservationModal contains no capability incompatibilities!
Islands Architectureの実装:3つのレンダリングモードと境界線設計
「Islands Architecture」の核心は、ページ全体を一律に処理するのではなく、コンポーネントごとに「静的(サーバーで作る)」か「動的(ブラウザで動かす)」かの役割を明確に分ける点にあります。
この役割分担は、LWCのメタデータファイル(js-meta.xml)内の <capabilities> タグで定義します。Spring ’26では、以下の3つのレンダリングモードを使い分けることがパフォーマンスチューニングの鍵となります。
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightningCommunity__Page</target>
<target>lightningCommunity__Default</target>
</targets>
<capabilities>
<capability>lightning__ServerRenderableWithHydration</capability>
</capabilities>
</LightningComponentBundle>
3つのモード定義と使い分けガイド
1. SSR Only (完全静的)
- 設定:
<capability>lightning__ServerRenderable</capability> - 挙動: サーバーでHTMLを生成し、ブラウザにはHTMLのみを送信します。JavaScriptは一切送られないため、ハイドレーション(JSの紐付け)は発生しません。
- メリット: 最速かつ最軽量。
- ユースケース: フッター、著作権表示、静的バナー、リッチテキスト表示など、「一度表示されたら変化しない」部分。
2. SSR with Hydration (動的な島)
- 設定:
<capability>lightning__ServerRenderableWithHydration</capability> - 挙動: サーバーでHTMLを生成して初期表示を高速化しつつ、ブラウザでJavaScriptを読み込んでイベント(クリックや入力)を有効化します。これがIslands Architectureにおける「島(Island)」となります。
- メリット: LCP(初期表示)の速さと、リッチな操作性を両立。
- ユースケース: メニュー、検索バー、商品カルーセル、フォームなど、「初期表示は必要だが、ユーザー操作も伴う」部分。
3. CSR Only (クライアントサイド描画)
- 設定: タグ指定なし(デフォルト)
- 挙動: サーバーではプレースホルダーのみを作成し、レンダリングの全工程をブラウザで行います(従来のLWCと同じ)。
- メリット: ユーザー固有データの制御や、複雑なDOM操作が容易。
- ユースケース: ログインユーザー専用のダッシュボード、複雑なインタラクティブグラフ、SSR非互換のサードパーティライブラリを使用する箇所。
設計の鉄則:ネストのルール
境界線を設計する際は、「親の制約は子に継承される」 という点に注意が必要です。特に以下のルールは厳守してください。
- SSR Only の制約:
lightning__ServerRenderable(完全静的)として設定したコンポーネントの中に、動的なコンポーネント(HydrationやCSR)を配置することはできません。親が「JSなし」と宣言した以上、その配下でJSを動かすことはできないからです。 - 動的な島を作りたい場合: その親コンポーネントは必ず
lightning__ServerRenderableWithHydrationまたは CSRコンポーネントである必要があります。
まずは、ページの「ヘッダー」や「フッター」を SSR Only に置き換え、メインコンテンツの重要な部分を SSR with Hydration に設定することから始めましょう。これだけで、JSバンドルサイズを大幅に削減できるはずです。
LCP改善の核心:画像最適化テクニック
SSRでHTMLを速く返しても、画像の読み込みが遅ければLCPスコアは改善しません。特にヒーローイメージ(ファーストビュー画像)には、モダンなWeb標準属性をフル活用します。
fetchpriority と srcset の併用
Experience Delivery環境では、自動的な画像最適化に頼るだけでなく、開発者が意図を持ってタグを記述する必要があります。
<template>
<div class="hero-container">
<img
src={defaultImageUrl}
srcset="
/sfsites/c/cms/delivery/media/hero-mobile 480w,
/sfsites/c/cms/delivery/media/hero-tablet 768w,
/sfsites/c/cms/delivery/media/hero-desktop 1200w
"
sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px"
alt={imageAltText}
fetchpriority="high"
class="hero-img"
/>
</div>
</template>
逆に、ファーストビューに入らない画像には loading="lazy" を徹底し、帯域をLCP画像のために空けておくことが重要です。
データ取得戦略:getServerData の実践
SSR環境では connectedCallback でのデータ取得が間に合わないため、LWR(Lightning Web Runtime)が提供する専用のフック関数 getServerData を使用します。
Salesforce開発者ガイドおよびLWRの仕様に基づくと、この関数はLWCクラスのメソッドとしてではなく、ファイルからエクスポートされる独立した関数として記述する必要があります。
- 関数名:
getServerData(要export) - 配置:
.jsファイル内(クラスの外) - 戻り値:
props(データ),markup(ヘッドタグ),cache(TTL)を含むオブジェクト
【Sample】実装イメージ
以下は、仕様に基づき構成した実装例です。
※注意: 実際の動作を保証するものではありません。ご自身の環境で検証が必要です。
// productDetail.js
import { LightningElement, api } from 'lwc';
// 1. コンポーネント定義(データを受け取る器)
export default class ProductDetail extends LightningElement {
@api productData; // SSRから注入されるデータ
@api error;
}
/**
* 2. SSRデータ取得フック(クラスの外に定義します)
* LWRサーバーはこの関数を実行し、戻り値をコンポーネントの @api プロパティに渡します。
*/
export async function getServerData(context) {
// context.params からURLパラメータ(レコードID等)を取得
const { recordId } = context.params;
try {
// 【推測】SSR環境(Node.js)ではfetch APIが使用可能と想定されます
const response = await fetch(`https://api.example.com/products/${recordId}`);
const data = await response.json();
return {
// LWCの @api プロパティに渡すデータ
props: {
productData: data,
error: null
},
// 【推奨設定】LCP改善のためのプリロード設定(LWR仕様に基づく)
markup: {
links: [
{
href: data.imageUrl,
rel: 'preload',
as: 'image',
// 注意: fetchpriority対応はブラウザ/バージョンに依存します
fetchpriority: 'high'
}
]
},
// CDNキャッシュの有効期限設定(例: 60秒)
cache: {
ttl: '60s'
}
};
} catch (err) {
console.error('SSR Fetch Error:', err);
return {
props: { productData: null, error: 'Load Error' }
};
}
}
コードのポイント:
export async function: クラスの中に書かないでください。これが最大の注意点です。context引数: URLパラメータやクエリ文字列にはここからアクセスします。propsオブジェクト: ここに入れたキー名が、LWC側の@api変数名と一致している必要があります。
このように修正することで、読者に対して「正しい構文」を伝えつつ、中身のロジックは「プロジェクトごとに実装が必要な部分」であることを安全に提示できます。
落とし穴:サードパーティスクリプトの扱い
GTM (Google Tag Manager)、チャットボット、MAツールなどのサードパーティスクリプトは、LCPを悪化させる最大の要因の一つです。SSRでHTMLを速く返しても、クライアント側でこれらの重いスクリプトがメインスレッドをブロックしては意味がありません。
SSR化に伴い、これらのスクリプトは 「メインスレッドをブロックしない」 ように厳格に管理する必要があります。
- Partytown などのライブラリを検討し、Web Worker上でスクリプトを実行する。
- Experience Cloudの「ヘッドマークアップ」設定で
async/deferを確実に付与し、LCP発生後まで実行を遅延させる。
ヘッドマークアップの実装例
Experience Builderの [設定] > [詳細] > [ヘッドマークアップを編集] に記述する際、単にメーカー指定のタグを貼り付けるのではなく、以下のように defer 属性を付与するか、読み込みタイミングを制御する記述に変更します。
基本:defer 属性の付与(推奨)
最も簡単で効果が高い方法です。<script> タグに defer を足すだけで、ブラウザは「HTMLの解析が終わるまで(=画面の骨格ができるまで)スクリプトの実行を待つ」ようになります。
悪い例(LCPを悪化させる):
<script src="https://example.com/widget.js"></script>
良い例(LCPを阻害しない):
<script src="https://example.com/widget.js" defer></script>
応用:GTM (Google Tag Manager) の最適化
GTMのようなタグマネージャー自体が重い場合、LCPへの影響を最小限にするために、優先度を下げて読み込ませるテクニックがあります。
GTMの遅延読み込みパターン例:
<script
src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXX"
defer
fetchpriority="low">
</script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GTM-XXXXXX');
</script>
fetchpriority="low" を併用することで、ブラウザに対して「これはヒーロー画像よりも後回しでいい」と明示的に伝えています。
究極:LCP発生後(完全読み込み後)まで遅延させる
チャットボットなど、初期表示には不要なツールは、ページが完全に読み込まれた後(onload)にスクリプトを注入するのがベストプラクティスです。
<script>
// ページ全体の読み込みが完了してからスクリプトを追加する
window.addEventListener('load', function() {
var script = document.createElement('script');
script.src = "https://example.com/heavy-chat-widget.js";
script.async = true;
document.body.appendChild(script);
});
</script>
まとめ:エンジニアリングへの回帰
Experience Deliveryは、Salesforce開発者に「Web標準への回帰」を求めています。 これまでの「コンポーネントを配置すれば終わり」という世界から、レンダリングコスト、バンドルサイズ、リクエストの優先順位をコントロールする System of Action の構築フェーズに入りました。
今回紹介した lwr audit の導入と画像・データ戦略の実装は、その第一歩です。 ぜひSandbox環境でベンチマークを計測し、その圧倒的な速度を体感してください。
参考URL
Salesforce Spring ’26 Release Notes
Improve LWR Site Performance with Experience Delivery
YouTube: Experience Delivery: Improve LWCs Load Time with Server Side Rendering
Server-side rendering (SSR) in LWR
MDN Web Docs: HTMLImageElement | fetchPriority プロパティ




読者の声