Salesforce B2C Commerce PWA Kit と SFRAのハイブリッド環境を作成してみる

※ これから記載する事項は、私が所属する会社とは一切関係のない事柄です。


今回は PWA Kit と SFRA のハイブリッド環境を作成してみたいと思います。
[こちらのヘルプ](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/phased-headless-rollouts.html)が参考になります。
作成にあたって２つのカートリッジが提供されています。

**SLAS プラグイン：**
https://github.com/SalesforceCommerceCloud/plugin_slas (要 Account Manager アクセス)
PWA Kit では [SLAS](https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas.html) を利用しているので、SFRAも同様にSLASを利用するためのカートリッジです。

**リダイレクトプラグイン：**
https://github.com/SalesforceCommerceCloud/plugin_redirect (要 Account Manager アクセス)
SFRA から PWA Kit へリダイレクトさせるためのカートリッジです。

このカートリッジを利用することで SFRA から PWA Kit への移行をスムーズに行うことができます。


## 作成する環境

SFRA と PWA Kit へのルーティングは **B2C Commerce についている eCDN でもできますし、[Heroku を使ったプロキシ](https://github.com/SalesforceCommerceCloud/composable-hybrid-dev-server)でも可能です** が今回は AWS Cloud Front を利用して同じドメイン配下に PWA Kit と SFRA を配置しました。


:::note info
追記：
現状では eCDN に SCAPI で自ら設定できるようになっています！
https://developer.salesforce.com/docs/commerce/commerce-api/references/cdn-api-process-apis?meta=createMrtRules
:::


![image (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/7f738825-bb3b-a204-a2ce-37901ed439f6.png)


ホーム、検索、カテゴリ、商品詳細、マイページ、ログインページはPWA Kitで作成し、それ以外のページ（主にカート以降のフロー）をSFRAで作成しました。

**ホーム画面（PWA Kit）**
![スクリーンショット 2022-06-20 11.02.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/f202d9fa-48d4-ee26-59a4-bf5e8b9a41c6.png)




**カート画面（SFRA）**
![スクリーンショット 2022-06-20 11.02.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/18f24b7b-af09-543a-36ef-79719b152201.png)




## 前提

* SFRAについてやPWAの環境構築については割愛します。
    * 参考として下記の記事も参照してみてください。
        * [Salesforce B2C Commerce PWA Kit のサンプルを構築してみる](https://qiita.com/syunyama/items/58433ae8e3c5fda7f37e)
        * [Salesforce B2C CommerceのSFRA開発環境セットアップ＆デプロイ方法](https://qiita.com/syunyama/items/21c71ac9c49b332e126d)
* AWS Cloud Frontの使い方についても割愛します。
* PWA Kit は v2.0.0 を利用しています
* サイトはSFRAのデフォルトであるRefArchを利用します




## 実装手順

### 1. カートリッジの設定

それぞれのカートリッジの README をご覧ください。



### 2. リダイレクトの設定

Business Manager から **マーチャントツール > サイト環境設定 > カスタムサイト環境設定グループ > PWA Kit Hybrid Plugin** に遷移し、下記のように設定し、カート以降のフロー以外は PWA Kit にリダイレクトするようにする。


|項目名	|値	|
|---	|---	|
|pwa_kit_redirect_home	|はい	|
|pwa_kit_redirect_product	|はい	|
|pwa_kit_redirect_category	|はい	|
|pwa_kit_redirect_search	|はい	|
|pwa_kit_redirect_account	|はい	|
|pwa_kit_redirect_cart	|いいえ	|
|pwa_kit_redirect_login	|はい	|
|pwa_kit_redirect_checkout	|いいえ	|
|pwa_kit_routes	|{"Home":"/","Product":"/[locale]/product/[productid]","Category":"/[locale]/category/[categoryid]","Search": "/[locale]/search?q=[searchterms]","Account": "/[locale]/account","Cart": "/[locale]/cart","Login": "/[locale]/login","Checkout": "/[locale]/checkout"}	|



### 3. Cloud Front の設定

* 今回はキャッシュは全くかけません。
* デフォルトでは SFRA に飛ばしています。
* `/mobify/*` と `デフォルト (*)` に関しては GET, HEAD 以外のメソッドを許容し、クエリパラメータと Cookie もオリジンにリクエストするようにしておきます。
* また、 `/mobify/*` は Authorization ヘッダーも通すように設定しておきます。（[設定方法](https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudfront-authorization-header/)）

![スクリーンショット 2022-06-20 12.51.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/5391d9ac-932b-43df-74c1-29d7be0526d6.png)

作成した際のCloud Frontのドメインはメモしておいてください。
![スクリーンショット 2022-06-20 14.55.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/5c985bed-eae2-4daf-f2bc-773f999c08cc.png)




### 4. PWA Kit のソースコードの修正

:::note warn
下記の「不要なルートの削除」と「SFRA のカート・チェックアウトへのリンクの修正」はヘルプの[Other Changes to PWA Kit Projects](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/phased-headless-rollouts.html#other-changes-to-pwa-kit-projects)でもできますが、挙動が少し違うので、お好きな方を選んでください。
:::

#### 不要なルートの削除

`app/routes.jsx` から不必要なルートを消しておきます。

```js:app/routes.jsx
  =====OMIT=====
  
   {
        path: '/account',
        component: Account
    },
    // {
    //     path: '/checkout',
    //     component: Checkout,
    //     exact: true
    // },
    // {
    //     path: '/checkout/confirmation',
    //     component: CheckoutConfirmation,
    //     exact: true
    // },
    {
        path: '/callback',
        component: LoginRedirect,
        exact: true
    },
    // {
    //     path: '/cart',
    //     component: Cart,
    //     exact: true
    // },
    {
        path: '/product/:productId',
        component: ProductDetail
    },
 
=====OMIT=====

{
    path: '*',
    component: withRouter((props) => {
        const {location} = props
        const urlParams = new URLSearchParams(location.search)
        useEffect(() => {
            const newURL = new URL(window.location)
            window.location.href = newURL
        })
        return null
    })
}

=====OMIT=====
```



#### SFRA のカート・チェックアウトへのリンクの修正

（どこに作っても構わないですが、）`app/utils/sfra.js`　に下記のコードを作成。

```js:app/utils/sfra.js
export const goCart = (locale, site) => {
    const newURL = new URL(window.location)
    newURL.pathname = `/on/demandware.store/Sites-${site.id}-Site/${locale.id.replace(
        '-',
        '_'
    )}/Cart-Show`
    newURL.search = ''
    window.location.href = newURL
}

export const goCheckout = (locale, site) => {
    const newURL = new URL(window.location)
    newURL.pathname = `/on/demandware.store/Sites-${site.id}-Site/${locale.id.replace(
        '-',
        '_'
    )}/Checkout-Begin`
    newURL.search = ''
    window.location.href = newURL
}
```

下記の例と同様に、`/checkout` や `/cart` が呼ばれている部分を修正していきます。

**例：**
`app/pages/cart/partials/cart-cta.jsx` 
`to="/checkout"` の代わりに `onClick={() => goCheckout(locale, site)}` を利用しています。

```js:app/pages/cart/partials/cart-cta.jsx
 ======OMIT======
// 追加
import {goCheckout} from '../../../utils/sfra'
import useLocale from '../../../hooks/use-locale'
import useSite from '../../../hooks/use-site'

const CartCta = () => {

    // 追加
    const locale = useLocale()
    const site = useSite()
    
    return (
        <Fragment>
            <Button
                // as={Link}
                // to="/checkout"
                
                // 追加
                onClick={() => goCheckout(locale, site)}
                
                width={['95%', '95%', '95%', '100%']}
                marginTop={[6, 6, 2, 2]}
                mb={4}
                rightIcon={<LockIcon />}
                variant="solid"
            >
                <FormattedMessage
                    defaultMessage="Proceed to Checkout"
                    id="cart_cta.link.checkout"
                />
            </Button>
            <Flex justify={'center'}>
                <VisaIcon height={8} width={10} mr={2} />
                <MastercardIcon height={8} width={10} mr={2} />
                <AmexIcon height={8} width={10} mr={2} />
                <DiscoverIcon height={8} width={10} mr={2} />
            </Flex>
        </Fragment>
    )
}

export default CartCta
```


他にも数ファイルありますので、`/checkout` や `/cart` でファイル検索してみてください。ざっとこんな感じのファイルが見つかるかと思います。

* app/pages/checkout/partials/checkout-header.jsx
* app/pages/cart/partials/cart-cta.jsx
* app/hooks/use-add-to-cart-modal.js
* app/components/order-summary/index.jsx
* app/components/_app/index.jsx





#### Service Worker の修正 

今回は CloudFront を利用するので`worker/main.js` に下記のコードを追加

```js:worker/main.js
=====OMIT====
// 追加
workbox.routing.registerRoute(
    /^https:\/\/.+\.cloudfront.net/,
    new workbox.strategies.NetworkFirst()
)
```

#### 

#### Cookie を利用するように修正

`app/commerce-api/auth.js` の54行目あたりのコメントアウトを外し、55行目をコメントアウトする

```js:app/commerce-api/auth.js
=====OMIT====
// To store tokens as cookies
// change the next line to
this._storage = this._onClient ? new CookieStorage() : new Map()
// this._storage = this._onClient ? new LocalStorage() : new Map()
=====OMIT====
```





#### ロケールを日本にし、 URL パスにデフォルトで表示するようにする

PWA Kit のロケールについての詳細は「[Salesforce B2C Commerce PWA Kitのマルチサイト・ロケールの設定](https://qiita.com/syunyama/items/060e74a6bb50d062e9e2)」を参考にしてください。

`config/default.js` 内を下記のように修正

```js:config/default.js
=====OMIT====
        // Customize how your 'site' and 'locale' are displayed in the url.
        url: {
            // Determine where the siteRef is located. Valid values include 'path|query_param|none'. Defaults to: 'none'
            // site: 'none',
            // Determine where the localeRef is located. Valid values include 'path|query_param|none'. Defaults to: 'none'
            locale: 'path',
            // This boolean value dictates whether or not default site or locale values are shown in the url. Defaults to: false
            showDefaults: true
        },
=====OMIT====
```


`config/sites.js` 内をロケールのデフォルトをに日本にするために、下記の内容に修正。

```js:config/sites.js
// Provide the sites for your app. Each site includes site id, and its localization configuration.
// You can also provide aliases for your locale. They will be used in place of your locale id when generating paths across the app
module.exports = [
    {
        id: 'RefArch',
        l10n: {
            supportedCurrencies: ['JPY'],
            defaultCurrency: 'JPY',
            defaultLocale: 'ja-JP',
            supportedLocales: [
                {
                    id: 'en-US',
                    // alias: 'us',
                    preferredCurrency: 'JPY'
                },
                {
                    id: 'ja-JP',
                    // alias: 'us',
                    preferredCurrency: 'JPY'
                }
            ]
        }
    }
]
```





### 5.  (必要あれば)同意画面の削除

既存ないしは新しくカートリッジを作成し、`/cartridge/controllers/ConsentTracking.js`
を下記の内容で作成し、コントローラを拡張する。
（今回は表示が邪魔だったので、同意画面なしで同意の有無に関わらずトラッキングを許可するように設定しましたが、実案件では要件に合わせて PWA ないしは SFRA を修正してください）

```js:/cartridge/controllers/ConsentTracking.js
'use strict';

/**
 * @namespace ConsentTracking
 */

var server = require('server');
var consentTracking = require('*/cartridge/scripts/middleware/consentTracking');
server.extend(module.superModule);

/**
 * ConsentTracking-Check : This endpoint is called every time a storefront page is rendered
 * @name Base/ConsentTracking-Check
 * @function
 * @memberof ConsentTracking
 * @param {middleware} - consentTracking.consent
 * @param {category} - sensitive
 * @param {renders} - isml
 * @param {serverfunction} - get
 */
server.replace('Check', consentTracking.consent, function (req, res, next) {
    req.session.privacyCache.set('consent', true);
    req.session.raw.setTrackingAllowed(true);
    next();
});

module.exports = server.exports();
```



### 6. ドメインの設定

#### SLASの設定

Business Manager で **マーチャントツール > サイト環境設定 > カスタムサイト環境設定グループ > SLAS Plugin** に遷移し、下記の値を設定

|項目名	|値	|
|---	|---	|
|redirectURI_SLAS	|https://{Cloud Frontのドメイン}/on/demandware.store/Sites-RefArch-Site/default/SLASCallback-RetrieveCode	|
|saveRefreshToken_Always	|いいえ	|
|ocapiSessionBridgeURI_SLAS	|https://{Cloud Frontのドメイン}/s/RefArch/dw/shop/v22_4/sessions	|

#### エイリアスの設定

Business Manager で **マーチャントツール > SEO > エイリアス** に遷移し、下記の値を設定し保存する


```json
{
    "__version": "1",
    "settings": {
        "http-host": "{Cloud Frontのドメイン}",
        "https-host": "{Cloud Frontのドメイン}"
    }
}
```






## 確認

Cloud Front のドメインにアクセスして、動作を確認してください。