Upgrade to 20.0.0
Why you should upgrade
This version has a lot of overall SEO improvements and also introduces full flexibility in creating your own widgets.
See release notes for more information
If you are using GTM and update to this version you will need to implement and start using the @geins/ralph-module-gtm
module instead, and change your GTM accordingly.
Pre-requisites
Before upgrading to 20, make sure you have already upgraded to 19. See guide here
Steps (required updates)
Update all list pages
The way Ralph UI fetches listPageInfo has completely changed and needs us to update all our list pages.
List pages with static list info
For list pages with static list info (like _search.vue
and all.vue
), update CaListPage
to use a static object for listInfo
.
<template>
<CaListPage :type="listType" :list-info="staticListInfo" />
</template>
<script>
export default {
...
data: () => ({
listType: 'all'
}),
computed: {
// Create the object to send to CaListPage. This is the same as the object you would get from the query
staticListInfo() {
const title = this.$t('ALL_PAGE_TITLE');
return {
name: title,
meta: {
title,
description: title
}
};
}
},
...
}
</script>
<template>
<CaListPage :type="listType" :list-info="staticListInfo" />
</template>
<script>
export default {
...
data: () => ({
listType: 'search'
}),
computed: {
// Create the object to send to CaListPage. This is the same as the object you would get from the query
staticListInfo() {
const title = this.$t('SEARCH_RESULTS_PAGE_TITLE', {
search: this.$route.params.search
});
return {
name: title,
meta: {
title,
description: title
}
};
}
},
...
}
List pages with dynamic list info
For list pages with dynamic list info (like for example pages/list/_category.vue
and pages/list/_list.vue
), update CaListPage
to use MixListInfo
// The same update goes for brand and discountCampaign
<template>
<CaListPage
:type="listType"
:list-info="listInfo"
:current-alias="currentAlias"
/>
</template>
<script>
import MixListInfo from 'MixListInfo';
export default {
...
mixins: [MixListInfo],
data: () => ({
listType: 'category'
}),
computed: {
currentAlias() {
return this.$route.params.category.split('/').pop();
}
},
...
</script>
<template>
<template>
<CaListPage :type="listType" :list-info="staticListInfo" />
</template>
<script>
import MixListInfo from 'MixListInfo';
export default {
...
mixins: [MixListInfo],
data: () => ({
listType: 'list'
}),
...
}
</script>
Use productImages instead of images
If you have overriden or ralph-ride any GraphQL queries, you will need to update them to use productImages
instead of images
.
Everywhere product.images
is used, change it to use product.productImages
instead. Note that this is an array of objects instead of an array of strings and you will find the filename in product.productImages[i].fileName
.
Example:
<template>
...
<CaImage
v-if="product.productImages && product.productImages.length > 0"
...
:filename="product.productImages[0].fileName"
...
/>
...
</template>
Unchanged example from launchpad with version ralph-ui 20.0.0 :
<template>
<component :is="baseTag" class="ca-product-card">
<div v-if="productPopulated" class="ca-product-card__image-wrap">
<NuxtLink
class="ca-product-card__image-link"
tabindex="-1"
:to="product.canonicalUrl"
:data-alias="product.alias"
@click.native="productClickHandler"
>
<CaImage
v-if="product.productImages && product.productImages.length > 0"
class="ca-product-card__image"
type="product"
:size-array="
$config.imageSizes.product.filter(
item =>
parseInt(item.descriptor) < 1150 &&
parseInt(item.descriptor) > 186
)
"
:ratio="$config.productImageRatio"
:filename="product.productImages[0].fileName"
:alt="product.brand.name + ' ' + product.name"
sizes="(min-width: 1360px) 248px, (min-width: 1024px) 18.23vw, (min-width: 768px) 30.73vw, 48vw"
/>
<CaImage
v-else
class="ca-product-card__image"
:ratio="$config.productImageRatio"
:src="require('~/assets/placeholders/product-image-square.png')"
:alt="product.brand.name + ' ' + product.name"
/>
</NuxtLink>
<CaToggleFavorite
class="ca-product-card__favorite"
:prod-alias="product.alias"
:prod-id="product.productId"
/>
</div>
<CaSkeleton
v-else
class="ca-product-card__image-wrap"
:ratio="$config.productImageRatio"
:radius="false"
/>
<div class="ca-product-card__info">
<NuxtLink
v-if="productPopulated"
:to="product.canonicalUrl"
@click.native="productClickHandler"
>
<CaBrandAndName
:brand="product.brand.name"
:name="product.name"
name-tag="h2"
/>
<CaPrice class="ca-product-card__price" :price="product.unitPrice" />
<CaCampaigns
v-if="product.discountCampaigns && product.discountCampaigns.length"
class="ca-product-card__campaigns"
:campaigns="product.discountCampaigns"
/>
<CaStockDisplay
class="ca-product-card__stock-display"
:stock="product.totalStock"
/>
</NuxtLink>
<div v-else>
<CaSkeleton width="30%" />
<CaSkeleton width="70%" />
<CaSkeleton width="50%" />
</div>
<CaButton
class="ca-product-card__buy-button"
type="full-width"
:loading="addToCartLoading"
@clicked="addToCartClick"
>
{{
skuId && product.totalStock.totalStock > 0
? $t('ADD')
: $t('READ_MORE')
}}
</CaButton>
</div>
</component>
</template>
<script>
import MixProductCard from 'MixProductCard';
export default {
name: 'CaProductCard',
mixins: [MixProductCard],
props: {},
data: () => ({}),
computed: {},
watch: {},
created() {},
methods: {}
};
</script>
<style lang="scss">
@import 'organisms/ca-product-card';
</style>
Update props for CaWidgetArea in CaListPage
CaWidgetArea
is no longer fetched by the MixListPage
mixin. Update the props of your widget area so that it fetches itself internally.
Example:
<template>
...
<CaWidgetArea
class="ca-list-page__widget-area"
family="Productlist"
area-name="The top part of the product list"
:filters="widgetAreaFilters"
:list-page-url="currentPath"
/>
...
</template>
Unchanged example from launchpad with version ralph-ui 20.0.0:
<template>
<div class="ca-list-page" :class="modifier">
<CaContainer>
<CaBreadcrumbs v-if="listInfo" :current="breadcrumbsCurrent" />
<CaSkeleton v-else class="ca-breadcrumbs" width="30%" />
<CaListTop v-if="!hideListInfo" :type="type" :list-info="listInfo" />
<CaImage
v-if="type === 'category' && listInfo && listInfo.primaryImage"
class="ca-list-page__image"
size="1280w"
type="categoryheader"
:alt="listInfo.name"
:filename="listInfo.primaryImage"
:ratio="271 / 1280"
sizes="(min-width: 1360px) 1320px, 96vw"
/>
<CaSkeleton
v-else-if="!listInfo"
class="ca-list-page__image"
:ratio="271 / 1280"
:radius="false"
/>
</CaContainer>
<CaWidgetArea
class="ca-list-page__widget-area"
family="Productlist"
area-name="The top part of the product list"
:filters="widgetAreaFilters"
:list-page-url="currentPath"
/>
<CaContainer>
<CaListFilters
v-if="showControls && selection"
:filters="filters"
:selection="selection"
/>
<CaActiveFilters
v-if="showControls && selection && $store.getters.viewportComputer"
:selection="selection"
:selection-active="filterSelectionActive"
@selectionchange="filterChangeHandler"
@reset="resetFilters"
/>
<LazyCaFilterPanel
:filters="filters"
:selection="selection"
:selection-active="filterSelectionActive"
:total-products="totalCount"
:total-filters-active="totalFiltersActive"
@selectionchange="filterChangeHandler"
@reset="resetFilters"
/>
<CaListSettings
v-if="showControls"
:active-products="totalCount"
:active-filters="totalFiltersActive"
:current-sort="selection.sort"
@sortchange="sortChangeHandler"
/>
<CaListPagination
v-show="currentMinCount > 1"
direction="prev"
:showing="showing"
:total-count="totalCount"
:min-count="currentMinCount"
:max-count="currentMaxCount"
:all-products-loaded="allProductsLoaded"
:loading="$apollo.queries.products.loading"
@loadprev="loadPrev"
@reset="resetCurrentPage"
/>
<CaProductList
:skip="currentMinCount - 1"
:page-size="pageSize"
:products="productList"
:products-fetched="productsFetched"
/>
<CaListPagination
v-if="showControls"
direction="next"
:showing="showing"
:total-count="totalCount"
:min-count="currentMinCount"
:max-count="currentMaxCount"
:all-products-loaded="allProductsLoaded"
:loading="$apollo.queries.products.loading"
@loadmore="loadMore"
@reset="resetCurrentPage"
/>
</CaContainer>
<CaWidgetArea
class="ca-list-page__widget-area"
family="Productlist"
area-name="The bottom part of the product list"
:filters="widgetAreaFilters"
:list-page-url="currentPath"
/>
</div>
</template>
<script>
/*
CaListPage is the main component for the product list pages.
*/
import MixListPage from 'MixListPage';
export default {
name: 'CaListPage',
mixins: [MixListPage],
props: {},
data: () => ({}),
computed: {},
watch: {},
created() {},
methods: {}
};
</script>
<style lang="scss">
@import 'organisms/ca-list-page';
</style>
Use GTM module instead of @nuxtjs/gtm
If you are using GTM, you will need to implement the @geins/ralph-module-gtm
module instead of the @nuxtjs/gtm
module to keep using GTM.
Install the module
npm install @geins/ralph-module-gtm
Add the module to nuxt.config.js
...
// Remove this
gtm: {
id: process.env.GTM_ID,
debug: ralphEnv !== 'prod',
respectDoNotTrack: false,
pageViewEventName: 'Page Impression',
pageTracking: false
},
modules: [
...
// Remove this row
'@nuxtjs/gtm',
// Add the new module
[
'@geins/ralph-module-gtm',
{
gtm: {
id: process.env.GTM_ID,
debug: process.env.RALPH_ENV === 'dev',
respectDoNotTrack: false
}
}
],
...
],
...
Steps (non-required but recommended updates)
Use MixBrandPage on brands page
Remove all functions and data on brands page and use the MixBrandPage
mixin instead.
Example:
...
<script>
import MixBrandPage from 'MixBrandPage';
export default {
name: 'BrandsPage',
mixins: [MixBrandsPage],
data: () => ({}),
computed: {},
watch: {},
methods: {},
meta: {
pageType: 'Brands Page'
}
}
</script>
Unchanged example from launchpad with version ralph-ui 20.0.0:
<template>
<div class="ca-brands-page">
<CaContainer>
<h1 class="ca-brands-page__title">{{ $t('BRANDS_TITLE') }}</h1>
<div v-if="brands && brands.length > 0" class="ca-brands-page__content">
<ul class="ca-brands-page__filter">
<li
v-for="(brandGroup, index) in brandsTree"
:key="index"
:class="{
'ca-brands-page__filter-item--active':
brandGroup.group === activeGroupFilter,
'ca-brands-page__filter-item--passive':
brandGroup.group != activeGroupFilter && isGroupFilter
}"
class="ca-brands-page__filter-item"
>
<CaClickable @clicked="setGroupFilter(brandGroup.group)">
{{ brandGroup.group }}
</CaClickable>
</li>
</ul>
<button
v-if="activeGroupFilter"
class="ca-brands-page__reset"
@click="setGroupFilter(activeGroupFilter)"
>
{{ $t('RESET_FILTERS') }}
</button>
<ul class="ca-brands-page__group">
<li
v-for="(brandGroup, index) in brandsTree"
:key="index"
:aria-hidden="[
brandGroup.group != activeGroupFilter && isGroupFilter
]"
class="ca-brands-page__group-item"
>
<span class="ca-brands-page__group-label">{{
brandGroup.group
}}</span>
<ul class="ca-brands-page__list">
<li
v-for="(brand, i) in brandGroup.brands"
:key="i"
class="ca-brands-page__list-item"
>
<NuxtLink class="ca-brands-page__link" :to="brand.canonicalUrl">
{{ brand.name }}
</NuxtLink>
</li>
</ul>
</li>
</ul>
</div>
<p v-else class="ca-brands-page__empty">
{{ $t('BRANDS_EMPTY') }}
</p>
</CaContainer>
</div>
</template>
<script>
import MixBrandsPage from 'MixBrandsPage';
/*
Renders the brands page.
*/
export default {
name: 'BrandsPage',
mixins: [MixBrandsPage],
data: () => ({}),
computed: {},
watch: {},
methods: {},
meta: {
pageType: 'Brands Page'
}
};
</script>
<style lang="scss">
@import './styles/pages/brands-page';
</style>
Fetch products only client side on start page
To improve loading speeds on start page, you can add a prop to the widget area to only fetch products client side.
The prop is called fetch-products-only-client-side
and should be set to true
.
<template>
...
<CaWidgetArea
family="Frontpage"
area-name="The front page area"
:fetch-products-only-client-side="true"
@dataFetched="$store.dispatch('loading/end')"
/>
...
</template>
Add .js to all store imports
For example, change
import { state, getters, mutations, actions } from 'checkout';
to
import { state, getters, mutations, actions } from 'checkout.js';
Example:
import { state, getters, mutations, actions } from 'checkout.js';
export default {
state,
getters,
mutations,
actions
};
Tips & tricks
This version comes with a new way of overriding standard CMS widget components. You can in your publicRuntimeConfig
set which component should render upon each requested widget type. For example, if you want to create your own "info block" widget based on the JSON widget, you could add widgetRenderTypesComponents
to your publicRuntimeConfig
like so:
return {
...
publicRuntimeConfig: {
...
widgetRenderTypesComponents: {
'JSON': 'CaInfoBlockWidget'
}
...
}
...
}
And then with npm run ralph-create
in your storefront, you can create the CaInfoBlockWidget
component and use it to render the widget. This widget must have the prop "configuration" which is the widget configuration object.
...
<script>
export default {
name: 'CaInfoBlockWidget',
props: {
// Widget configuration object
configuration: {
type: Object,
required: true
}
}
...
};
...
</script>
Instead of building your own, use the Geins CMS module as a base for creating an array of custom widgets. Read more in the documentation of the module.