Sana Assistant (online)
Table of Contents

Server-Side Rendering Support

Content blocks can optionally provide server-side rendering (SSR) support to preload data during the server rendering process. This ensures that the necessary data is available when the component is rendered on the server, improving initial page load performance and SEO.

preloadDataForServerRendering Method

The ContentBlock<M> component can optionally provide a preloadDataForServerRendering method with the following signature:

preloadDataForServerRendering?: (options: ServerRenderingPreloadOptions<M>) => void;

This method is invoked during the server-side rendering process to allow the content block to request the data required for its rendering.

ServerRenderingPreloadOptions Type

The ServerRenderingPreloadOptions<M> type contains the following properties:

Property Type Description
store { getState(): DefaultRootState; dispatch(action: Action): void } The add-on store with methods to get state and dispatch actions.
model M The content block model.
context AppContext Current application context data available for add-ons.
page ServerRenderingProductPage | ServerRenderingProductGroupPage | ServerRenderingProductListPage | ServerRenderingSearchPage | null The current page information.

Store Object

The store object provides access to:

  • getState() - Gets the current state from the store
  • dispatch(action) - Dispatches an action to the store

AppContext Type

For detailed information about the AppContext type and its properties, see App Context.

Page Types

The page property can be one of the following types or null:

ServerRenderingProductPage

type ServerRenderingProductPage = {
  /** The page product. */
  product: Product | CalculatedProduct;
};

For more information about the Product and CalculatedProduct types, see useProductContext hook.

ServerRenderingProductGroupPage

type ServerRenderingProductGroupPage =
  & NonNullable<ReturnType<typeof useProductGroupContext>>
  & Omit<NonNullable<ReturnType<typeof useProductGroupFilters>>, 'applyFiltersSelection'>;

For more information about the useProductGroupContext, see useProductGroupContext hook. For more information about the useProductGroupFilters, see useProductGroupFilters hook.

ServerRenderingProductListPage

type ServerRenderingProductListPage = NonNullable<ReturnType<typeof useProductListPage>> & {
  /** The list of products available on the page. */
  products: Array<Product | ProductGroup>;
};

For more information about the useProductListPage, see useProductListPage hook. For more information about the Product and CalculatedProduct types, see useProductLine hook.

ServerRenderingSearchPage

type ServerRenderingSearchPage = NonNullable<ReturnType<typeof useSearchPage>> & {
  /** The list of products available on the page. */
  products: Array<Product | ProductGroup>;
};

For more information about the useSearchPage, see useSearchPage hook. For more information about the Product and CalculatedProduct types, see useProductLine hook.

Example with Server-Side Rendering

import type { ContentBlock } from 'sana/types';
import { memo, useEffect, useRef } from 'react';
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { requestProductSet } from 'behavior';
import ProductTiles from './ProductTiles';
import { useHasAbilities, parseSortOption } from 'sana/utils';

type Model = {
  maximumProductsToShow: number;
  productSetId: string;
  sortOption: string;
};

const ProductSetBlock: ContentBlock<Model> = ({ model, id }) => {
  const [canViewCatalog] = useHasAbilities('VIEW_CATALOG');
  const dispatch = useDispatch();
  const productsKey = createProductsKey(model);
  const productSet = useSelector(s => s.productSets && s.productSets[productsKey]);

  const modelRef = useRef<Model>();
  const modelExpired = modelRef.current && !shallowEqual(modelRef.current, model);
  modelRef.current = model;

  useEffect(() => {
    if (!canViewCatalog)
      return;

    if (!productSet || productSet.expired || modelExpired)
      dispatch(createRequestProductsAction(productsKey, modelRef.current!));
  });

  const products = productSet?.products;
  if (!products?.length)
    return null;

  return <ProductTiles products={products} id={id} />;
};

const ProductSetBlockMemo: ContentBlock<Model> = memo(ProductSetBlock);

// Server-side rendering support
ProductSetBlockMemo.preloadDataForServerRendering = ({ model, store, context, page }) => {
  // Check if user has permission to view catalog using context
  if (!context.user.canViewCatalog)
    return;

  const productsKey = createProductsKey(model);
  
  // Dispatch action to preload product data during SSR
  store.dispatch(createRequestProductsAction(productsKey, model));
};

export default ProductSetBlockMemo;

function createRequestProductsAction(productsKey: string, model: Model) {
  const { maximumProductsToShow, productSetId, sortOption } = model;
  const sorting = sortOption ? [parseSortOption(sortOption)] : null;

  return requestProductSet(productsKey, productSetId, maximumProductsToShow, sorting);
}

function createProductsKey(model: Model) {
  return `${model.productSetId}_${model.maximumProductsToShow}_${model.sortOption}`;
}

Implementation Notes

In this example:

  • The content block renders a set of products using the ProductTiles component
  • The ProductTiles component, requestProductSet action and add-on redux store implementations are out of scope for this example
  • The example uses the useHasAbilities hook to check user permissions
  • The preloadDataForServerRendering method checks user permissions via context.user.canViewCatalog
  • It dispatches a Redux action to preload product data during server-side rendering
  • The same logic is used both in the component's useEffect and the SSR preload method
  • The method ensures that product data is available when the page is rendered on the server