Dockershim (Docker) runtime deprecation on AKS and how to fix our Sitecore containers

If you upgraded AKS recently (to v1.23+), you might have noticed some containers stopped working, most of the time failing to start with messages like “Error: failed to start containerd task “solr”: hcs::System::CreateProcess solr: The system cannot find the file specified.: unknown“.

As we spent quite some time researching those issues and also contacted Sitecore support, I’ve decided to write this post so it can be helpful to anyone else facing the same kind of issues.

Even if Docker runtime is still available in v1.23, it comes with Containerd by default so you will get that kind of exception. But bare in mind Docker is going to be fully removed on v1.24 so I suggest you take action as soon as possible to avoid blocking upgrading or facing issues later.

About Sitecore default images

In case you were making use of Sitecore images =< v10.0.2 then you will find those failing to start, mostly on the solr-init and mssql-init containers.

Sitecore has fixed the images for the following versions:

  • 10.0.3
  • 10.1.2
  • 10.1.3
  • 10.2.0
  • 10.2.1

Please notice that if you are referring to the image versions using the “two-digit” tag, then you’re good to go, as would be getting its latest version.

About the runtime deprecation

AKS announced the deprecation of Docker in version 1.20:

Dependency on Docker explained

container runtime is software that can execute the containers that make up a Kubernetes pod. Kubernetes is responsible for orchestration and scheduling of Pods; on each node, the kubelet uses the container runtime interface as an abstraction so that you can use any compatible container runtime.

In its earliest releases, Kubernetes offered compatibility with one container runtime: Docker. Later in the Kubernetes project’s history, cluster operators wanted to adopt additional container runtimes. The CRI was designed to allow this kind of flexibility – and the kubelet began supporting CRI. However, because Docker existed before the CRI specification was invented, the Kubernetes project created an adapter component, dockershim. The dockershim adapter allows the kubelet to interact with Docker as if Docker were a CRI compatible runtime.

Switching to Containerd as a container runtime eliminates the middleman. All the same, containers can be run by container runtimes like Containerd as before. But now, since containers schedule directly with the container runtime, they are not visible to Docker. So any Docker tooling or fancy UI you might have used before to check on these containers is no longer available.

You cannot get container information using docker ps or docker inspect commands. As you cannot list containers, you cannot get logs, stop containers, or execute something inside a container using docker exec.

Please refer to the official documentation for deeper details:

About our custom images

Ok, so now that things got a bit clear, and we know Sitecore base images are fixed in the latest versions (at least for v10), what about our custom ones?

So far, I’ve identified some changes required on our Dockerfile to make it work as expected in Containerd runtime.

ENTRYPOINT and CMD

The syntax is slightly different, I’ll share examples so it’s even easier to understand the changes.

This is the original Dockerfile:

ENTRYPOINT .\StartInit.ps1 -ResourcesDirectory $env:RESOURCES_PATH -SqlServer $env:SQL_SERVER -SqlAdminUser $env:SQL_ADMIN_LOGIN -SqlAdminPassword $env:SQL_ADMIN_PASSWORD -SitecoreAdminUsername $env:SITECORE_ADMIN_USERNAME -SitecoreAdminPassword $env:sitecore_admin_password -SitecoreUserPassword $env:SITECORE_USER_PASSWORD -SqlElasticPoolName $env:SQL_ELASTIC_POOL_NAME -DatabasesToDeploy $env:DATABASES_TO_DEPLOY -PostDeploymentWaitPeriod $env:POST_DEPLOYMENT_WAIT_PERIOD `
    -DatabaseUsers @(...)@]

The updated one:

ENTRYPOINT ["powershell.exe", ".\\StartInit.ps1", "-ResourcesDirectory $env:RESOURCES_PATH", "-SqlServer $env:SQL_SERVER", "-SqlAdminUser $env:SQL_ADMIN_LOGIN", "-SqlAdminPassword $env:SQL_ADMIN_PASSWORD", "-SitecoreAdminUsername $env:SITECORE_ADMIN_USERNAME", "-SitecoreAdminPassword $env:sitecore_admin_password", "-SitecoreUserPassword $env:SITECORE_USER_PASSWORD", "-SqlElasticPoolName $env:SQL_ELASTIC_POOL_NAME", "-DatabasesToDeploy $env:DATABASES_TO_DEPLOY", "-PostDeploymentWaitPeriod $env:POST_DEPLOYMENT_WAIT_PERIOD", `
    "-DatabaseUsers @(...)@]

Please note that now is needed to specify the shell we’re using.

Here is the updated solr-init Dockerfile:#

ENTRYPOINT ["powershell.exe", ".\\Start.ps1", "-SitecoreSolrConnectionString $env:SITECORE_SOLR_CONNECTION_STRING", `
    "-SolrCorePrefix $env:SOLR_CORE_PREFIX_NAME", `
    "-SolrSitecoreConfigsetSuffixName $env:SOLR_SITECORE_CONFIGSET_SUFFIX_NAME", `
    "-SolrReplicationFactor $env:SOLR_REPLICATION_FACTOR", `
    "-SolrNumberOfShards $env:SOLR_NUMBER_OF_SHARDS", `
    "-SolrMaxShardsPerNodes $env:SOLR_MAX_SHARDS_NUMBER_PER_NODES", `
    "-SolrXdbSchemaFile .\\data\\schema.json", `
    "-SolrCollectionsToDeploy $env:SOLR_COLLECTIONS_TO_DEPLOY"] 

I hope this helps clarify and fix your environments deployed on AKS as you get it upgraded.

Multisite support for Sitecore JSS – Next.js using Vercel’s Edge Middleware – Demo on Sitecore Demo Portal (XM + Edge)!

I’ve come across the requirement for supporting a multi-site Sitecore-SXA approach with a single rendering host (Next.js app).

With this approach, we want to lower the costs by deploying to a single Vercel instance and making use of custom domains or sub-domains to resolve the sites.

If you have a look at the Sitecore Nextjs SDK and/or the starter templates, you’ll notice that there is no support for multi-site, so here I’ll go through a possible solution for this scenario where we need also to keep the SSG/ISR functionality from Next.js/Vercel.

The approach

To make it work we basically need to somehow resolve the site we’re trying to reach (from hostname or subdomain) and then pass it through the LayoutService and DictionaryService to resolve those properly.

As we’ve also enabled SSG, we’ll need to do some customization to the getStaticPaths so it generates the sitemap for each site.

Resolving the site by custom domains or subdomains

As I mentioned in the title of the post, I’ll be using Edge Middleware for that, so I’ve based this on the examples provided by Vercel, check the hostname-rewrites example!

For more details on Edge Middleware, please refer to my previous post!

Dynamic routes

Dynamic Routes are pages that allow you to add custom parameters to your URLs. So, we can then add the site name as a param to then pass it through the layout and dictionary services. For more details on dynamic routing, check the official documentation and the example here!

Demo!

We now know all the basics, let’s move forward and make the needed changes to make it work.

For demoing it, I’m just creating a new Sitecore Next.js JSS app by using the JSS initializer and the just recently released Sitecore Demo Portal! – Check this great blog from my friend Neil Killen for a deep overview of it!

Changes to the Next.js app

To accomplish this, as already mentioned, we have to play with dynamic routing, so we start by moving the [[…path]].tsh to a new folder structure under ‘pages’: pages/_sites/[site]/[[…path]].tsh

Then we’ve to create the middleware.ts file in the root of src. The code here is quite simple, we get the site name from the custom domain and then update the pathname with it to do an URL rewrite.

import { NextRequest, NextResponse } from 'next/server'
import { getHostnameDataOrDefault } from './lib/multisite/sites'

export const config = {
  matcher: ['/', '/_sites/:path'],
}

export default async function middleware(req: NextRequest): Promise<NextResponse> {
  const url = req.nextUrl.clone();
  // Get hostname (e.g. vercel.com, test.vercel.app, etc.)
  const hostname = req.headers.get('host');

  // If localhost, assign the host value manually
  // If prod, get the custom domain/subdomain value by removing the root URL
  // (in the case of "test.vercel.app", "vercel.app" is the root URL)
  const currentHost =
    //process.env.NODE_ENV === 'production' &&
    hostname?.replace(`.${process.env.ROOT_DOMAIN}`, '');
  const data = await getHostnameDataOrDefault(currentHost?.toString());

  // Prevent security issues – users should not be able to canonically access
  // the pages/sites folder and its respective contents.
  if (url.pathname.startsWith(`/_sites`)) {
    url.pathname = `/404`
  } else {
    // rewrite to the current subdomain
    url.pathname = `/_sites/${data?.subdomain}${data?.siteName}${url.pathname}`;
  }
  
  return NextResponse.rewrite(url);
}

You can see the imported function getHostnameDataOrDefault called there, so next, we add this to /lib/multisite/sites.ts

const hostnames = [
  {
      siteName: 'multisite_poc',
      description: 'multisite_poc Site',
      subdomain: '',
      rootItemId: '{8F2703C1-5B70-58C6-927B-228A67DB7550}', 
      languages: [
        'en'
      ],
      customDomain: 'www.multisite_poc_global.localhost|next12-multisite-global.vercel.app',
      // Default subdomain for Preview deployments and for local development
      defaultForPreview: true,
    },
    {
      siteName: 'multisite_poc_uk',
      description: 'multisite_poc_uk Site',
      subdomain: '',
      rootItemId: '{AD81037E-93BE-4AAC-AB08-0269D96A2B49}', 
      languages: [
        'en', 'en-GB'
      ],
      customDomain: 'www.multisite_poc_uk.localhost|next12-multisite-uk.vercel.app',
    },
]
// Returns the default site (Global)
const DEFAULT_HOST = hostnames.find((h) => h.defaultForPreview)

/**
 * Returns the data of the hostname based on its subdomain or custom domain
 * or the default host if there's no match.
 *
 * This method is used by middleware.ts
 */
export async function getHostnameDataOrDefault(
  subdomainOrCustomDomain?: string
) {
  if (!subdomainOrCustomDomain) return DEFAULT_HOST

  // check if site is a custom domain or a subdomain
  const customDomain = subdomainOrCustomDomain.includes('.')

  // fetch data from mock database using the site value as the key
  return (
    hostnames.find((item) =>
      customDomain
        ? item.customDomain.split('|').includes(subdomainOrCustomDomain)
        : item.subdomain === subdomainOrCustomDomain
    ) ?? DEFAULT_HOST
  )
}

/**
 * Returns the site data by name
 */
export async function getSiteData(site?: string) {
  return hostnames.find((item) => item.siteName === site);
}

/**
 * Returns the paths for `getStaticPaths` based on the subdomain of every
 * available hostname.
 */
export async function getSitesPaths() {
  // get all sites
  const subdomains = hostnames.filter((item) => item.siteName)

  // build paths for each of the sites
  return subdomains.map((item) => {
    return { site: item.siteName, languages: item.languages, rootItemId: item.rootItemId }
  })
}

export default hostnames

I’ve added the custom domains I’d like to use later to resolve the sites based on those. I’ve defined 2 as I want this to work both locally and then when deployed to Vercel.

Changes to the getStaticProps

We keep the code as it is in the [[…path]].tsx, you’d see that the site name is now part of the context.params (add some logging there to confirm this)

[[…path]].tsx

Changes to page-props-factory/normal-mode.ts

We need now to get the site name from the context parameters and send it back to the Layout and Dictionary services to set it out. I’ve also updated both dictionary-service-factory.ts and layout-service-factory constructors to accept the site name and set it up.

normal-mode.ts
fictionary-service-factory.ts
layout-service-factory.ts

Please note that the changes are quite simple, just sending the site name as a parameter to the factory constructors to set it up. For the dictionary, we are also setting the root item id.

Changes to getStaticPaths

We have now to modify that in order to build the sitemap for SSG taking all sites into account. The change is also quite simple:

// This function gets called at build and export time to determine
// pages for SSG ("paths", as tokenized array).
export const getStaticPaths: GetStaticPaths = async (context) => {
  ...

  if (process.env.NODE_ENV !== 'development') {
    // Note: Next.js runs export in production mode
    const sites = (await getSitesPaths()) as unknown as Site[];
    const pages = await sitemapFetcher.fetch(sites, context);
    const paths = pages.map((page) => ({
      params: { site: page.params.site, path: page.params.path },
      locale: page.locale,
    }));

    return {
      paths,
      fallback: process.env.EXPORT_MODE ? false : 'blocking',
    };
  }

  return {
    paths: [],
    fallback: 'blocking',
  };
};

As you can see, we are modifying the fetcher and sending the site’s data as an array to it so it can process all of them. Please note the site param is now mandatory so needs to be returned in the paths data.

Custom StaticPath type

I’ve defined two new types I’ll be using here, StaticPathExt and Site

Site.ts
StaticPathExt.ts

We need to make some quick changes to the sitemap-fetcher-index.ts now, basically to send back to the plugin the Sites info array and to return the new StaticPathExt type.

import { GetStaticPathsContext } from 'next';
import * as plugins from 'temp/sitemap-fetcher-plugins';
import { StaticPathExt } from 'lib/type/StaticPathExt';
import Site from 'lib/type/Site';

export interface SitemapFetcherPlugin {
  /**
   * A function which will be called during page props generation
   */
  exec(sites?: Site[], context?: GetStaticPathsContext): Promise<StaticPathExt[]>;
}

export class SitecoreSitemapFetcher {
  /**
   * Generates SitecoreSitemap for given mode (Export / Disconnected Export / SSG)
   * @param {GetStaticPathsContext} context
   */
  async fetch(sites: Site[], context?: GetStaticPathsContext): Promise<StaticPathExt[]> {
    const pluginsList = Object.values(plugins) as SitemapFetcherPlugin[];
    const pluginsResults = await Promise.all(
      pluginsList.map((plugin) => plugin.exec(sites, context))
    );
    const results = pluginsResults.reduce((acc, cur) => [...acc, ...cur], []);
    return results;
  }
}

export const sitemapFetcher = new SitecoreSitemapFetcher();

And last, we update the graphql-sitemap-service.ts to fetch all sites and add its info to get returned back to the getStaticPaths

async exec(sites: Site[], _context?: GetStaticPathsContext): Promise<StaticPathExt[]> {
    let paths = new Array<StaticPathExt>();
    for (let i = 0; i < sites?.length; i++) {
      const site = sites[i]?.site || config.jssAppName;
      this._graphqlSitemapService.options.siteName = site;
      this._graphqlSitemapService.options.rootItemId = sites[i].rootItemId;
      if (process.env.EXPORT_MODE) {
        // Disconnected Export mode
        if (process.env.JSS_MODE !== 'disconnected') {
          const p = (await this._graphqlSitemapService.fetchExportSitemap(
            pkg.config.language
          )) as StaticPathExt[];
          paths = paths.concat(
            p.map((page) => ({
              params: { path: page.params.path, site: site },
              locale: page.locale,
            }))
          );
        }
      }
      const p = (await this._graphqlSitemapService.fetchSSGSitemap(
        sites[i].languages || []
      )) as StaticPathExt[];
      paths = paths.concat(
        p.map((page) => ({
          params: { path: page.params.path, site: site },
          locale: page.locale,
        }))
      );
    }
    return paths;
  }

We’re all set up now! Let’s now create some sample sites to test it out. As I already mentioned, I’m not spinning up any Sitecore instance locally or Docker containers but just using the new Demo Portal, so I’ve created a demo project using the empty template (XM + Edge). This is really awesome, I haven’t had to spend time with this part.

Sitecore Demo Portal

I’ve my instance up and running, and it comes with SXA installed by default! Nice ;). So, I’ve just created two sites under the same tenant and added some simple components (from the JSS boilerplate example site).

Sitecore Demo Portal instance

From the portal, I can also get the Experience Edge endpoint and key:

Sitecore Demo Portal

Note: I’ve had just one thing to do and I’ll give feedback back to Sitecore on this, by default there is no publishing target for Experience Edge, even though it comes by default on the template, so I’ve to check the database name used in XM (it was just experienceedge) and then created a new publishing target.

The first thing is to check the layout service response gonna work as expected, so checked the GraphQL query to both XM and Experience Edge endpoints to make sure the sites were properly resolved.

From XM: https://%5Bsitecore-demo-instance%5D/sitecore/api/graph/edge/ui

GraphQL playground from XM

From Experience Edge: https://edge-beta.sitecorecloud.io/api/graphql/ide

GraphQL playground from Experience Edge

All good, also checked that the site ‘multisite_poc_uk‘ is also working fine.

Now, with everything set, we can test this out locally. The first thing is to set the environment variables so those point to our Experience Edge instance.

  • JSS_EDITING_SECRET: (from Demo Portal)
  • SITECORE_API_KEY: (from Demo Portal)
  • SITECORE_API_HOST=https://edge-beta.sitecorecloud.io
  • GRAPH_QL_ENDPOINT=https://edge-beta.sitecorecloud.io/api/graphql/v1
  • FETCH_WITH=GraphQL

Let’s run npm run start:connected in our src/rendering folder!

Note: I’ve added hosts entries to test this out locally:

127.0.0.1 www.multisite_poc_global.localhost
127.0.0.1 www.multisite_poc_uk.localhost
npm run start:connected

If everything went well, you should be able to see that (check the logging we added in the getStaticProps previously).

UK Site
Global Site

Cool! both sites are properly resolved and the small change I’ve made to the content bock text confirms that.

Let’s now run npm run next:build so we test the SSG:

npm run next:build

Deploying to Vercel

We’re all set to get this deployed and tested in Vercel, exciting!

I won’t go through the details on how to deploy to Vercel as I’ve already written a post about it, so for details please visit this post!

Couple of things to take into account:

  • I don’t push my .env file to the GitHub repo, so I’ve set all the environment variables in Vercel itself.
  • I’ve created 2 new custom domains to test this. Doing that is really straightforward, in Vercel got to the project settings, and domains and create those:
Vercel custom domains

I’ve pushed the changes to my GitHub repo that is configured in Vercel so a deployment just got triggered, check build/deployment logs and the output!

Looking good! let’s try out the custom domains now:

https://next12-multisite-global.vercel.app/

Global site

https://next12-multisite-uk.vercel.app/

UK site

I hope you find it interesting, you can find the code I’ve used for this example in this GitHub repo.

If you have a better or different approach to resolve multisite within a single Next.js app, please comment! I’d love to hear about other options.

I’d like also to say thanks to Sitecore for this Portal Demo initiative, it’s really helpful to speed up PoC and demos to customers!

Thanks for reading!

Deliver dynamic content at the speed of static with Next.js Middleware and Vercel Edge!

Intro

In this post I’d like to share a topic that we’ve presented together with my friend Ehsan Aslani during the Sitecore User Group France, an event that I’ve also organized with my friends Ugo Quaisse and Ramkumar Dhinakaran in Paris at the Valtech offices, find more details and pictures about the event here.

About Edge Middleware

At the time we presented this topic in the UG, the Edge Functions in Vercel were in beta version, now we got the good news from Vercel that they released Next.js version 12.2 that includes Middleware stable among other amazing new experimental features like:

On top of this new release, Vercel also introduced a new concept that makes a little bit of confusion around it, Edge Functions != Edge Middleware. In the previous version, the middleware was deployed to Vercel as an Edge Function, while now it’s a “Middleware Edge”.

Edge Functions (still in beta)

Vercel Edge Functions allow you to deliver content to your site’s visitors with speed and personalization. They are deployed globally by default on Vercel’s Edge Network and enable you to move server-side logic to the Edge, close to your visitor’s origin.

Edge Functions use the Vercel Edge Runtime, which is built on the same high-performance V8 JavaScript and WebAssembly engine that is used by the Chrome browser. By taking advantage of this small runtime, Edge Functions can have faster cold boots and higher scalability than Serverless Functions.

Edge Functions run after the cache, and can both cache and return responses.

Edge Functions

Middleware Functions

Edge Middleware is code that executes before a request is processed on a site. Based on the request, you can modify the response. Because it runs before the cache, using Middleware is an effective way of providing personalization to statically generated content. Depending on the incoming request, you can execute custom logic, rewrite, redirect, add headers, and more, before returning a response.

Edge Middleware allows you to deliver content to your site’s visitors with speed and personalization. They are deployed globally on Vercel’s Edge Network and enable you to move server-side logic to the Edge, close to your visitor’s origin.

Middleware uses the Vercel Edge Runtime, which is built on the same high-performance V8 JavaScript and WebAssembly engine that is used by the Chrome browser. The Edge Runtime exposes and extends a subset of Web Standard APIs such FetchEventResponse, and Request, to give you more control over how you manipulate and configure a response, based on the incoming requests. To learn more about writing Middleware, see the Middleware API guide.

Edge Middleware

Benefits of Edge Functions

  • Reduced latency: Code runs geographically close to the client. A request made in London will be processed by the nearest edge node to London, instead of Washington, USA.
  • Speed and agility: Edge Functions use Edge Runtime, which, due to its smaller API surface, allows for a faster startup than Node.js
  • Personalized content: Serve personalized cached content based on attributes such as visitor location, system language, or cookies

About nested middleware in beta

With the stable release of middleware in Next.js v12.2, nested middleware is not supported anymore, details here.

In the beta version, it was possible to create different “_middleware.ts” files under specific folders so we can control when those are executed. Now, only one file at the app’s root is allowed and we need to add some logic to handle that by checking the parsed URL, like:

// <root>/middleware.ts
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/about')) {
    // This logic is only applied to /about
  }

  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    // This logic is only applied to /dashboard
  }
}

Common usages

Demo time!

In the demo I’ve prepared for the UG, I used edge functions for doing a bit of geolocation, playing with cookies, A/B testing, rewrites, and feature-flag enablement.

To start I’ve just created an empty project using the nextjs CLI:

npx create-next-app@latest --typescript

Then, inside the recently created app folder, and then check localhost:

npm run dev

We are all set to start testing out the middleware, for doing that, we get started by creating a new file in the root folder, name it “middleware.ts”. Let’s add some code there to test how it works:

import { NextRequest, NextResponse, NextFetchEvent } from "next/server";

export function middleware (req: NextRequest, event: NextFetchEvent) {  
    const response = NextResponse.next()
    response.headers.set('x-sug-country', 'FR')
    return response
}

This will just simply add a response header and return it, refresh your browser and check the headers, our recently added “x-sug-country” should be there:

For this demo, I’ve created some simple pages:

– Pages
|- about
|- aboutnew
|- index
|- featureflag
|- featureflags
|- abtest
|- index

A/B Testing

The idea was to do some A/B testing on the about page, so for doing that I’ve used ConfigCat an easy-to-use tool for managing feature-flags enablement, that also has some options to target audience, so I’ve created my “newAboutPage” flag with 50% option:

The following code is what we need to place in our middleware, and it will basically get the value flag value from ConfigCat and store it in a cookie. Note the usage here of URL Redirects and Rewrites, cookies management, feature flags, and of course, A/B testing, all running as a middleware function, that when deployed to Vercel will be executed on the edge network, close to the visitor origin, with close to zero latency.

export function middleware (req: NextRequest) {  
    if (req.nextUrl.pathname.startsWith('/about')) {
        const url = req.nextUrl.clone()

        // Redirect paths that go directly to the variant
        if (url.pathname != '/about') {
            url.pathname = '/about'
            return NextResponse.redirect(url)
        }

        const cookie = req.cookies.get(ABOUT_COOKIE_NAME) || (getValue('newaboutpage') ? '1' : '0')

        url.pathname = cookie === '1' ? '/about/aboutnew' : '/about'

        const res = NextResponse.rewrite(url)

        // Add the cookie if it's not there
        if (!req.cookies.get(ABOUT_COOKIE_NAME)) {
            res.cookies.set(ABOUT_COOKIE_NAME, cookie)
        }

        return res
      }
...

Let’s test it and by clicking on “Remove Cookie and Reload” you’ll be getting both variants with 50% probability:

Feature flags

In the demo, I’ve also added the features flag page where I’m rendering or hiding some components depending on their flag enablement, again by using ConfigCat for this:

The “sugconfr” flag, that you can see it’s disabled, and the “userFromFrance” that also checks the country parameter and only returns true if it’s France, so we can see here how easy we can personalize based on geolocation.

Let’s have a look at the code we’ve added to the middleware:

export function middleware (req: NextRequest) {  
    if (req.nextUrl.pathname.startsWith('/about')) {
        ...
      }

      if (req.nextUrl.pathname.startsWith('/featureflag')) {
        const url = req.nextUrl.clone()
  
        // Fetch user Id from the cookie if available 
        const userId = req.cookies.get(COOKIE_NAME_UID) || crypto.randomUUID()
        const country = req.cookies.get(COOKIE_NAME_COUNTRY) || req.geo?.country
        const sugfr = req.cookies.get(COOKIE_NAME_SUGFR) || (getValue(COOKIE_NAME_SUGFR) ? '1' : '0')

        const res = NextResponse.rewrite(url)
        
        // Add the cookies if those are not there
        if (!req.cookies.get(COOKIE_NAME_COUNTRY)) {
            res.cookies.set(COOKIE_NAME_COUNTRY, country)
        }

        if (!req.cookies.get(COOKIE_NAME_UID)) {
            res.cookies.set(COOKIE_NAME_UID, userId)
        }

        if (!req.cookies.get(COOKIE_NAME_SUGFR)) {
            res.cookies.set(COOKIE_NAME_SUGFR, sugfr)
        }

        return res
      }

Again, we get the values and store it in cookies. Then we use the feature flags to show or hide some components, as we can see here:

If we load the page with the “sugconfr” disabled, we will get this:

So, let’s enable it back from ConfigCat, publish the changes and reload the page:

Now the page looks different, the SUGFR component is showing up. As you can see, the other component where we have chosen to enable only for users coming from France is still not showing, this is because we are testing from localhost so of course, there is no geolocation data coming from the request. So, let’s deploy this app to Vercel so we can also test this part and check how it looks running in the Edge.

Deploying the Edge Middleware to Vercel

As I have my Vercel app installed in my GitHub repo, the integration is so simple that we can just push those changes to the repo and wait for Vercel to deploy it. For details on how to set this up, please check my previous post about Deploying a Sitecore JSS-Next.js App with SSG & ISR to Vercel (from zero to live)

Note: make sure you add the ConfigCat API Key to the environment variables in Vercel before deploying:

If you have a look at the deployment logs, you will see that it created the edge function based on our middleware:

If we check the site now, as we are now getting geolocation data from the user’s request, the component is showing up there:

You can check logs by going to functions sections from the Vercel dashboard, which is really cool for troubleshooting purposes:

This was just a quick example of how to start using this middleware feature from Next.js and Vercel’s Edge Network, which enable us to move some backend code from the server to the edge, making those calls super fast with almost no latency. Now that is already stable we can start implementing those for our clients, there are multiple usages, another quick example where we can implement those are for resolving multisite by hostnames for our Sitecore JSS/Next.js implementation.

You can find the example app code here in this GitHub repo. The app is deployed to Vercel and accessible here.

There is a great “getting started” video from Thomas Desmond, check it!

You can find a lot of great examples from Vercel Edge Functions repo as well and of course the official documentation.

I hope you enjoyed this reading and don’t wait, go and have fun with middleware!

Converting Sitecore MVC sites to Jamstack with Headless Services and JSS – Next.js

In this post I’ll be showing an approach to convert existing Sitecore MVC applications to the Jamstack architecture, it’s time to think about how to modernize our old-fashioned Sitecore apps to benefit from modern tech stacks capabilities like headless, SSG, ISR, multi-channel, etc.

Architecture

Jamstack architecture for existing Sitecore MVC sites is possible because of the ability of the Sitecore Layout Service to render MVC components to HTML, and include them in its output.

Jamstack architecture with Next.js. Ref: here

The publishing and rendering process consists of the following steps:

  1. The Layout Service outputs MVC components as HTML, embedded in its usual service output.
  2. The Layout Service output is published to the Content Delievry with each page/route, allowing it to be queried by Sitecore headless SDKs such as Next.js.
  3. The Next.js application queries the Layout Service output for the route and passes it into one or more placeholder components.
  4. Based on the lack of a componentName property in the layout data, the Placeholder component in the Sitecore Next.js SDK renders the Sitecore component directly as HTML into the pre-rendered document.

Prerequisites

  • Sitecore version 10.2+ – An upgrade of your MVC application would be needed.
  • Sitecore Headless Services module version 19+.

Demo!

To make things easier for this demo, I’m using the “Basic Company – Unicorn” site from the Sitecore Helix examples, you can find the repo here.

The first step is to upgrade the solution to 10.2, you can also find my open PR with the upgrade here.

Then, we need to add the Headless Services to our CM and CD images. You can find the final code here, which also adds on top of it a Next.js RH app image.

At this point, we have our MVC application up and running on Sitecore 10.2 and Headless Services are also installed. We are now ready to start making some changes to the app so we can make it work with JSS.

Prepare the MVC site to be compatible with JSS App

First of all, we need to create our API key in order to allow the Layout Service to communicate through our Sitecore instance. For that, we simply create an item under /sitecore/system/Settings/Services/API Keys

Make sure to keep CORS and controllers to allow * for this demo

To enable editing and static generation support in the JSS app, we have to make the site root to inherit from /sitecore/templates/Foundation/JavaScript Services/App template:

To enable editing support, we need the layout to inherit the template /sitecore/templates/Foundation/JavaScript Services/JSS Layout .

Now, we need to configure the Layout Service Placeholders field. This field determines which placeholder information to include in the Layout Service response data.

Inspect the Layout Service reponse

We can have a look now and analyze the Json we are getting from the Layout Service by visiting the endpoint:

https://www.basic-company-unicorn.localhost/sitecore/api/layout/render?item={97479C6B-BB30-4A15-AFD1-2C89F207E9D6}&sc_apikey={B10DB745-2B8A-410E-BDEC-07791190B599}

Layout Service response

We can see in the response, that we are getting the placeholders we included previously (main, header and footer).

Configure the Sitecore Layout Service to output HTML for MVC renderings

Let’s now go and configure the “Hero Banner” component to render HTML instead of Json:

Done, let’s publish this change and see what we get in the Layout Service response for this component:

So, here we go, we can find the HTML now in the contents. Let’s enable the HTML output on all the other MVC renderings and publish those changes, in the meantime, let’s create our JSS app.

Create the Nextjs JSS app

Let’s open a terminal and navigate to the src folder (..\examples\helix-basic-unicorn\src\Project\BasicCompany). We now run the JSS CLI command to create a new app, here we can choose if we want to fetch data with REST or GraphQL, also the prerendering on SSG or SSR:

jss create basic-company nextjs --empty --fetchWith GraphQL --prerender SSG

The JSS app is now created. Let’s set it up and connect it to our Sitecore instance. Run the following CLI command:

cd basic-company
jss setup

Provide the following values:

1- Is your Sitecore instance on this machine or accessible via network share? [y/n]: y
2- Path to the Sitecore folder (e.g. c:\inetpub\wwwroot\my.siteco.re): ..\examples\helix-basic-unicorn\docker\deploy\website
3- Sitecore hostname (e.g. http://myapp.local.siteco.re; see /sitecore/config; ensure added to hosts): https://www.basic-company-unicorn.localhost/
4- Sitecore import service URL [https://www.basic-company-unicorn.localhost/sitecore/api/jss/import]:
5- Sitecore API Key (ID of API key item): {B10DB745-2B8A-410E-BDEC-07791190B599}
6- Please enter your deployment secret (32+ random chars; or press enter to generate one):

Now we can deploy the config (check the files creates under sitecore/config). For this we run the following CLI command

jss deploy config

Prepare the NextJs App to render our content

Let’s update the Layout.tsx to add our placeholders (header, main, footer):

Also copy the “basic-company.css” from the website folder into the “src/assets” folder and update the _app.tsx with this :

All good, time to connect and test it! Run the following CLI command:

jss start:connected
HTTP://localhost:3000

Yay! visit http://localhost:3000 and you can see the Basic Company MVC site rendered as a JSS App, this is ready to be deployed and make it statically generated, but let’s move one step forward and start converting one of the components to React, as I see this approach to incrementally start your migration to JSS (React).

Experience Editor compatibility

Let’s double-check that our Experience Editor is still working as expected:

Start converting components from MVC (C#/Razor) to Next.js (JavaScript/React) incrementally

Let’s duplicate the “Hero Banner” rendering in Sitecore, change the template to make it a “Json Rendering”, rename it to “HeroBanner” to make it compliant with React naming conventions, and disable the “Render as HTML” checkbox. Also, make sure the “component name” field is set to “HeroBanner”. Then add this new component to the Homepage next to the MVC one.

Duplicated HeroBanner component

Publish the rendering and check again the Layout Service response, now, you should be able to see the two versions of the component, the one in HTML and the Json:

Good! We got the expected results on the Layout Service response, if we go now and refresh our JSS App, we will see that the component is added but still lacking its React implementation:

Create the React component through the component scaffolding

To create the React implementation of the component we created, just run the following in the terminal (always from the JSS App root):

jss scaffold BasicContent/HeroBanner 

Have a look at the files created, make some changes to the React implementation (BasicContent/HeroBanner.tsx)

import { Text, Field, ImageField } from '@sitecore-jss/sitecore-jss-nextjs';
export type HeroBannerProps = {
  fields: {
    Title: Field<string>;
    Subtitle: Field<string>;
    Image: ImageField;
  };
};
const HeroBanner = ({ fields }: HeroBannerProps): JSX.Element => {
  const bannerStyle = {
    backgroundImage: `url(${fields.Image?.value?.src})`,
  };
  return (
    <section className="hero is-medium is-black" style={bannerStyle}>
      <div className="hero-body">
        <div className="container">
          <h1>React!</h1>
          <Text field={fields.Title} tag="h1" className="title" />
          <Text field={fields.Subtitle} tag="h2" className="title" />
        </div>
      </div>
    </section>
  );
};
export default HeroBanner;

Now both MVC and React components are living on the same site, I kept both to make it more visual, but the proper way of migrating would be just replacing the MVC rendering.

I hope you find this interesting, you can find the complete solution here, it’s a fork of the Sitecore Helix Examples and added on top the headless services, Sitecore 10.2 upgrade, a NextJS rendering host, and app.

Thanks for reading!

Ref Official documentation

Start your commerce solution in minutes with Vercel, Next.js Commerce template, and the new Sitecore OrderCloud® integration – Part II

In my previous post, I’ve shared how easy is to get your Next.js Commerce solution up and running powered by Sitecore OrderCloud®. Please have a look at the previous post before getting into this reading as this is a continuation that focuses on the development environment, the local solution setup, and the CI/CD approach.

Local solution setup

Let’s get back to our Github repo and clone it locally, then open it on Visual Studio Code.

Starter kit solution on VS Code

We have our solution cloned locally, let’s open a terminal now and run npm install to install all dependency packages needed by the solution.

npm i

Vercel CLI

The Vercel’s command-line interface enables instant cloud deployment and local development.
To learn more, visit the official documentation, let’s get started now by installing the CLI:

npm i -g vercel

Now we can just run “vercel link” to link our local repo with the Vercel project.

Use your Vercel credentials to login, choose your scope, and Vercel will magically recommend the proper project to link to. Veryfi all this information and proceed, if everything went good, you should see a green tick saying your project is now linked.

vercel link

Now we need to get in our local all the environment variables we got created on Vercel:

Environment Variables needed for communicating with the Sitecore OrderCloud API

We can speed up the process of creating those variables locally by using the Vercel CLI command “vercel env pull

You can see now that a new .env file got created and contains all the variables we need:

environment variables locally imported

Also, the previous step created the “vercel” folder where we can find the project.json configuration file.

Let’s test it out!

We got now everything in place, so let’s just run the app locally in develop mode by running “npm run dev

npm run dev

Pum! Another unexpected exception 😦

After some analysis, I noticed there is an issue in this file (packages/commerce/src/config.cgs), while trying to build the path to the next.config file under packages:

const commerceNextConfig = importCwd(path.join(provider, 'next.config'))

I just updated with the hardcoded path and it seemed to fix the issue. Check this open issue for more details, or you can also refer to this PR that seems to be fixing the issue.

Build succeed this time 😉
Storefront site at localhost

The app is now running locally in development mode and connected to Sitecore OrderCloud.

Let’s test our CI/CD

Let’s make a quick change on our project, test it locally, push it to Github and then deploy to Vercel.

So, in order to make some changes to the homepage, open and edit the pages/index.tsx file:

As you can see, I’m moving the marquee before the grid, and adding a heading text (note that you’d need to first add the import):

Here we go! As we’re running the app on development mode, we get feedback instantly, so get back to the local site in the browser and refresh it:

The marquee is now at the top and right after the heading text we added (“Sitecore OrderCloud Rocks!“).

Cool, so now that we’re fine with our local test, we commit and push to the repo…

Just verify we pushed the changes to Github

Go back to the Vercel portal and check that the build got automatically triggered 🙂

Amazing, isn’t it? Our changes are now deployed and pushed live to Vercel:

That’s it! The main reason behind this post was to show how easy is to get started with Sitecore OrderCloud, Next.js Commerce Templates, and Vercel. The starter kit gives you everything to start working with and for start learning this new technology stack as well. So don’t waste time and go try out this by yourself!

I hope you found it interesting, and see you soon with more Sitecore related stuff!

Start your commerce solution in minutes with Vercel, Next.js Commerce template, and the new Sitecore OrderCloud® integration – Part I

In this post, I’ll describe the steps to get a full development environment from scratch, using the Next.js Commerce template integrated with OrderCloud and deploying to Vercel.

Sitecore OrderCloud®

Sitecore OrderClooud is an API-First, headless cloud platform for B2B, B2C, and B2X commerce solutions. It powers custom e-commerce experiences, order management, and B2B marketplace applications for some of the world’s most well-known brands.

If you don’t have your account yet, go here and create one, you can sign up for free!

Vercel – Next.js Commerce

Next.js Commerce is an all-in-one React starter kit for high-performance e-commerce sites. You can clone, deploy, and fully customize with a few clicks.

And again, if you don’t have an account yet, go here and create one, you can sign up for free!

If you want to learn more about Next.js, don’t hesitate to go through the official documentation, it’s really good and useful.

Let’s go and get it done!

The first step is to login into Vercel and start a new project, then we just need to choose the Next.js Commerce template:

The Vercel templates

After we choose the Next.js Commerce template, we give the Git repo a name, and click on create:

Creating the starter kit Github repo

You can now go and check what Vercel has created for you in the Github repo:

Github

If you have experience developing Next.js apps, this repo would look familiar to you.

If you look at the readme file, you will see there is a demo site powered by Sitecore OrderCloud®!

Let’s get back and continue with our setup!

We have our Github repo, let’s skip the add integrations step for now and go straight to the deploy:

Build/deploy running for the first time…
The project got deployed to Vercel

Yas! your project got built and deployed to Vercel, you can now go and browse it (amazing isn’t it?)

The Commerce site is already up and running!

Setting up the Sitecore OrderCloud® integration

The site you’re browsing now already has some content, but as we are not integrating with any e-commerce platform yet, is just statically stored and generated. Let’s change that and switch our e-commerce solution to be powered by Sitecore OrderCloud®.

For doing that, go back to your Vercel project and then go to the Settings section, then click on Integrations and search for the OrderCloud by browsing the Marketplace

Browsing the integrations Marketplace

You would now see the Sitecore OrderCloud integration under the Commerce section:

SitecoreOrderCloud integration

Then click on Add Integration and choose your Personal Account as Vercel Scope. Click on continue and then select the project we just created for the OrderCloud integration.

Add the integration and then login to your Sitecore OrderCloud account.

In the next step, you’re prompted to choose your OrderCloud Marketplace. You would see that you can choose an existing one, but this time I want to create a new one “Seed new Marketplace (ID: “NEW”)

You can see the progress and logs, yeah Vercel is creating everything on OrderCloud for you!

Get back now to your Vercel project, settings and check the Environment Variables. You can see the key values created in the previous step:

Check your Sitecore OrderCloud® Marketplace

Now, login to Sitecore OrderCloud, go to Marketplaces and you should be now able to see the newly created “Vercel Commerce

Let’s try this out. Go to the API Console and then select “Catalogs“, click on send to make the API call, and check the results:

Copy the ID (solitary-storefront) so you can now browse categories by this Catalog ID:

Let’s browse the products now, copy the category ID (shirts) and make the request from the Products section:

Time to deploy it!

We just checked that the data is created on OrderCloud and we can easily query the API to get the results. Our Vercel integration is ready and now is time to deploy it so get our app consuming OrderCloud data.

just get back to your Vercel project, go to the deployments section, select the latest green one and redeploy it:

Uuuups! the build failed this time… That’s weird and unexpected, but let’s check logs and see what’s going on:

As I mentioned in the previous step, the OrderCloud integration created the environment variables to make the app communicate with OrderCloud.

Ok, that’s odd, it seems the “COMMERCE_PROVIDER” variable is not set properly. Let’s follow the logs recommendations and update it from “ordercloud” to “@vercel/commerce-ordercloud“. Save it and redeploy:

Yas! it seems to be good now, and the build is going good, you can also see in the logs how the site is being statically generated but making requests to the OrderCloud API this time:

You can see now our site getting data from OrderCloud!

Let’s make a quick update on OrderCloud and check changes in our storefront.

Let’s make an update on the Black Hat product. For that, go to the “Products” search for the “Black Hat” and then choose “PUT Create or update a product“. Change the price from 80 to 40, and the name to “OrderCloud Black Hat“:

We can query again to make sure the price got updated:

Get back to the site and check the updated product:

We have now our e-commerce solution deployed to Vercel, powered by Sitecore OrderCloud, and our Next.js app created on our Github repo.

In the next post, I’ll be exploring and sharing the local solution setup and the CI/CD so you can get an idea of how easy is to start building your solution with this tech stack.

I hope you found this post useful!

If you are interested in this topic, please don’t hesitate to watch the full demo example from Rob Earlam on YouTube.

Sitecore JSS – NEXT.js – Exploring the Incremental Site Regeneration (ISR).

Next.js allows you to create or update static pages after you’ve built your site. Incremental Static Regeneration (ISR) enables developers and content editors to use static-generation on a per-page basis, without needing to rebuild the entire site. With ISR, you can retain the benefits of static while scaling to millions of pages.

Static pages can be generated at runtime (on-demand) instead of at build-time with ISR. Using analytics, A/B testing, or other metrics, you are equipped with the flexibility to make your own tradeoff on build times.

Consider an e-commerce store with 100,000 products. At a realistic 50ms to statically generate each product page, the build would take almost 2 hours without ISR. With ISR, we can choose from:

Faster Builds → Generate the most popular 1,000 products at build-time. Requests made to other products will be a cache miss and statically generate on-demand: 1-minute builds.

Higher Cache Hit Rate → Generate 10,000 products at build-time, ensuring more products are cached ahead of a user’s request: 8-minute builds.

Exploring ISR

In my previous post, I’ve created a JSS-Next.js app that we deployed to Vercel. I also created a WebHook to trigger a full rebuild in Vercel (SSG). Now, I’ll explain how the ISR works in this same app.

Fetching Data and Generating Paths

Data:

ISR uses the same Next.js API to generate static pages: getStaticProps.
By specifying revalidate: 5, we inform Next.js to use ISR to update this page after it’s generated.

Check the src/pages/[[…path]].tsx file and the getStaticProps function:

Paths:

Next.js defines which pages to generate at build-time based on the paths returned by
getStaticPaths. For example, you can generate the most popular 1,000 products at build-time by returning the paths for the top 1,000 product IDs in getStaticPaths.

With this configuration, I’m telling Next.js to enable ISR and to revalidate every 5 sec. After this time period, the first user making the request will receive the old static version of the page and trigger the revalidation behind the scenes.

The Flow

  1. Next.js can define a revalidation time per-page (e.g. 5 seconds).
  2. The initial request to the page will show the cached page.
  3. The data for the page is updated in the CMS.
  4. Any requests to the page after the initial request and before the 5 seconds window will show the cached (hit) page.
  5. After the 5 second window, the next request will still show the cached (stale) page. Next.js triggers a regeneration of the page in the background.
  6. Once the page has been successfully generated, Next.js will invalidate the cache and show the updated product page. If the background regeneration fails, the old page remains unaltered.

Page Routing

Here’s a high-level overview of the routing process:

In the diagram above, you can see how the Next.js route is applied to Sitecore JSS.

The [[…path]].tsx Next.js route will catch any path and pass this information along to getStaticProps or getServerSideProps on the context object. The Page Props Factory uses the path information to construct a normalized Sitecore item path. It then makes a request to the Sitecore Layout Service REST API or Sitecore GraphQL Edge schema to fetch layout data for the item.

Demo!

So, back to our previously deployed app in Vercel, login to Sitecore Content Editor and make a change on a field. I’m updating the heading field (/sitecore/content/sitecoreverceldemo/home/Page Components/home-jss-main-ContentBlock-1) by adding “ISR Rocks!”. We save the item and refresh the page deployed on Vercel. (Don’t publish! this will trigger the webhook that is defined in the publish:end event).

After refreshing the page, I can still see the old version:

But, if I keep checking what is going on in the ngrok, I can see the requests made to the layout service:

So, after refreshing again the page, I can see the changes there!

So, it got updated without the need of rebuilding and regenerating the whole site.

That’s it! I hope this post helps to understand how the ISR works and how to start with it on your Sitecore JSS implementation.

Thanks for reading and stay tuned for more Sitecore stuff!

Deploying a Sitecore JSS-Next.js App with SSG & ISR to Vercel (from zero to live)

In this post, I’ll share the steps to get our Next.js Sitecore App deployed into Vercel on some simple steps. Vercel is the creator of Next.js and now also a Sitecore partner. To avoid a huge and extensive post, I won’t be writing about Next.js, Vercel, JSS, etc. instead, please find some useful links with references to all those at the end of the blog post.

Getting the JSS app locally

The first step is to have the Sitecore JSS-Next.js app running locally. For simplifying things, we’ll be creating it with the help of JSS CLI. Before starting, make sure you’ve Node.js installed locally.

We just get started by running the following command to install the JSS CLI (more info here):

npm install -g @sitecore-jss/sitecore-jss-cli

Now, we can start to play with the CLI, so let’s create the app:

jss create sitecoreverceldemo nextjs

We give it a name (sitecoreverceldemo) and a framework (nextjs).

Connected Mode

The first thing we need to do is to create our API key so our JSS app can communicate to our Sitecore instance. In this demo, I’m running a local Sitecore instance, but it could be also a containerized one.

So, for doing that, we login into Sitecore and go to /sitecore/system/Settings/Services/API Keys and we create a new item, give it a name, and copy the ID somewhere, this gonna be our API key moving forward.

Now, we run the following command to start setting up it:

jss setup

We just follow the wizard and set the proper values for our Sitecore instance, API Key, import service URL, etc. If all went well, then you should see something like this:

Note that you’ve to add the recently created hostname to the hosts file in windows and ISS (sitecore.vercel.demo).

We’re now ready to deploy the configs, and right after that, the items, for doing that we simply run the following commands:

jss deploy config
jss deploy items --includeContent --includeDictionary

This will run the import to Sitecore and create the sample items. We can now build the app by running:

jss build

That’s it! We can now start our JSS App in connected mode:

jss start:connected

Code Repository

We now run some Git commands to push our code to GitHub. You can find the one I’m using for this demo here.

Vercel

Now, that we are done with our JSS Next.js app, we can have fun deploying it to Vercel. The first good news here, you can go and create your free account for testing purposes 🙂

Another good thing about Vercel is that it connects to GitHub, GitLab, and Bitbucket, so it makes things really easy. So, let’s go and import our GitHub repo there:

Let’s click on import and then configure our project. (Note: skip the Teams creation step to avoid having to get a trial account).

BUT as we have our Sitecore instance locally (or running on a container) we’ve to somehow expose our localhost to the internet. For that, we can use this amazing tool: Ngrok.

As I’m using the free version, it generates random URLs, but this is enough for our demo. Don’t forget to add those to the IIS binding and hostfile (if you’re running Sitecore locally).

Back to Vercel, we have to setup some environment variables:

SITECORE_API_KEY: The Sitecore API key we created in the previous step.
SITECORE_API_HOST: The URL generated by NGRock.
JSS_EDITING_SECRET: Your secret token. The JSS_EDITING_SECRET is optional for deployments but necessary if you want to use the Experience Editor with your Next.js Vercel deployment.

In next.config.js, replace:

const publicUrl = process.env.PUBLIC_URL;

with the following:

const publicUrl = process.env.VERCEL_URL ? https://${process.env.VERCEL_URL} : process.env.PUBLIC_URL;

So, it takes the URL we defined as an environment variable in Vercel.

Now everything is set and ready to be deployed. Let’s get back to Vercel, and deploy!

You’ll need to also update the hostname in sitecore/config/sitecoreverceldemo.config.

If everything was well configured, you should be able to see the requests to the headless services while Next.js is generating the static site during the building, something like this:

Et voila! The site is now live!

The publishing webhook

We need now to trigger the deployment if the content gets changed in the CMS. For that, we create a deploy hook in Vercel:

In the settings/Git section, we choose to create a deploy hook, we give it a name and a branch (develop in this case).

Create the hook and copy the URL. Let’s create now a config patch in Sitecore that will trigger it on publush:end

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:search="http://www.sitecore.net/xmlconfig/search/">
  <sitecore>
    <javaScriptServices>
      <publishWebHooks type="Sitecore.JavaScriptServices.AppServices.WebHooks.WebHooks, Sitecore.JavaScriptServices.AppServices">
        <hooks hint="list:AddWebHook">
          <hook type="Sitecore.JavaScriptServices.AppServices.WebHooks.WebHookDefinition, Sitecore.JavaScriptServices.AppServices">
            <name>Vercel - Publish Hook</name>
            <url>https://api.vercel.com/v1/integrations/deploy/prj_f4NcDuveH3zCP9a6pObWlaApER14/T7ATu61aej</url>
            <method>POST</method>
            <site>sitecoreverceldemo</site>
            <body></body>
          </hook>
        </hooks>
      </publishWebHooks>
    </javaScriptServices>
  </sitecore>
</configuration>
  • url: Required. The URL of the hook to be invoked.
  • method: Optional. The HTTP method for invoking webhook. Possible values are POST or GET. The default method is POST.
  • site: Optional. The sites which should trigger the webhook when published. By default, Sitecore will trigger the webhook for every published item. If you provide the site parameter, the webhook will be invoked if the published item root is an ancestor, descendant, or equal to the configured site’s root item.

Let’s test it, make a quick change in Sitecore and publish the item (heading field):

After publishing, we can see that the deploy hook got triggered in Vercel:

Refresh the site, and we can see our changes there:

In my next post I’ll explain a bit how the ISR works as it deserves specific writing about it.

That’s it! As you can see the steps to setup your CI/CD with Vercel is quite straightforward. I hope you find this post useful and helps you with the first steps of getting into Next.js and Vercel. Stay tuned for more Sitecore stuff!

References:

Sitecore Moosend – Part III: Campaigns, Automation, Templates, Designer and Subscription Forms

In the previous posts (Part I and Part II) we’ve explored the Mailing Lists and Segmentation, Custom Fields, a bit about Automation and both the front end and API integration approaches.

Let’s go deeper into the other features and functionalities that Moosend provides out of the box and some quick examples on how to make use of those in your website.

Subscription Forms

Creating a form to, for example, get users subscribing to your newsletter is easy and straightforward. Just go to the Lead Generation section and then clikc on the Subscription Forms tab. Click on create new subscription form and you will see the different options Moosend proposes:

I’ll choose and create a Modal Pop-up, that will do the same than we have done in the previous posts via the API, adding a user to a mailing list with the birthday as optional field, so then we can apply our segmentation based on that.

After we choose a name and we go to the next step, you will see the option to make use of the designer.

The Designer

Moosend has a really cool and user friendly interface to build your email templates and forms. It already comes with a lot of different templates ready to be used making the user life’s easier. Also the editor, with drag & drop functionalities and grids helps to edit those or to create them from scratch.

We can then preview the form and then, if we’re good with it, use it.

In the editor, we go to the form settings and we assign our mailing list and also include the custom field (Date of Birth)

We enable all fields and keep only Email and Name as mandatory, the Date of Birth is optional.

Our form is ready, we clock on save and continue to get back to the Subscription Forms wizard.

In the “Visibility Settings” you will find a lot of different options to handle how and when to show up the modal in your webiste. You can also define and use rules for showing/hidding it.

The form is now ready to be published. Moosend offers different alternatives, like publishing straight to your configured site, embed a code or link to it.

After publishing, we can see and test our form, as you can see I’m just publishing it to my page:

I’ve subscribed two more users through the form (miguel@test4.com and miguel@test5.com). Let’s go and check how the mailing list looks now:

The new users have been added, please note the source is now = Form. If we check the segmentation, you will see that those users doesn’t belong to the “API Subscribers” as has been added though the newsletter form.

Campaigns

Let’s now create a campaign that we will use to put all things together, for doing that, we go to the Campaigns section and fill out the required data:

After we complete this section, we can assign this campaign to any of out Mailing Lists or Segments, for this example I’m assigning the “API Subscriber” segment from our list “My Testing List“.

The next step is to select an HTML or just text version, we will be using the HTML version for this example.

Same as when creating a Form, you can make use of the builtin templates or use de editor to edit it or start one from scratch.

We can now test the campaign, if we are good with it, we are then ready to enable and schedule it.

That’s very much it, we have now our campaign ready and sending emails to our segmentated mailing list.

If everything went well, you should receive a notification like this:

I hope you find this Moosend post series interesting and useful, it was just a quick and simplistic example just to give an overview on the different features and options but I hope you get the main idea and ways of working with Moosend, combining all those tools and featrures you can enrich your marketing and get it to the next level.

Thanks for reading and stay tuned for more Sitecore acquisition products overviews!

Sitecore Moosend – Part II: Mailing Lists, Custom Fields and API Integration

In my previous post I’ve shared a quick overview on Moosend and some of the main features. I advise you to take a look at the previous one before proceeding with this reading.

Today we will explore a bit more in depth the mailing lists, custom fields and also the API implementation approach.

Mailing Lists and Segments

In the previous post we created a Mailing List and added subscribers using the front end approach, applying Segmentation to it, we can improve the efficency of our marketing campaign by targeting the audience based on the data gathered from the users (custom fields) and the events recorded by Moosend.

Custom Fields

We can define in this section custom fields that we then can use for gathering data from the user, on top of the default ones (Name, Email and Mobile). We can use those afterwards for automation, segmentation, etc.

Let’s create a new custom field (Date of Birth) and make it optional:

Our custom field is now created and we can use it for our example. Check the generated tag: “recipient:Date of Birth“: you can make use of this token for pesonalize your campaigns.

Segments

Let’s for example take our previously created list “My Testing List” and create a new segmentation based on the Subscription Method = API Integration AND Date of Birth field < 01-01-2010“.

We give a name and then add a criteria, so here we’re creating a segmentation where we fetch “all contacts that subscribed through the API integration method and provided a Date of Birth before 01-01-2010“.

API Integration

As mentioned, this time we’ll be doing the integration it with the API approach. Moosend provides an API wrapper (Javascript or C# .NET) that makes working with it really straightforward, you can find the Nuget package here.

First of all, go to the setting section and then click in API key, copy it and save for later:

Now we can create our service class on .NET Core that will interact with the Moosend API:

MoosendService.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Moosend.Api.Client.Common.Models;
using MyApp.WebApi.Configuration;

namespace MyApp.WebApi.Services
{
    public class MoosendService : IMoosendService
    {
        public MoosendService(IOptions<MoosendSettings> settings)
        {
            MoosendSettings = settings.Value;
        }

        private MoosendSettings MoosendSettings { get; }

        public async Task<Moosend.Api.Client.Common.Models.Subscriber> AddSubscriberAsync(string name, string email,
            DateTime dob)
        {
            var mailingListId = new Guid(MoosendSettings.MailingListID);
            var apiKey = new Guid(MoosendSettings.ApiKey);
            var apiClient = new Moosend.Api.Client.MoosendApiClient(apiKey);
            var customFields = new Dictionary<string, string> {{"Date of Birth", dob.ToLongDateString()}};
            var member = new SubscriberParams()
            {
                Email = email,
                Name = name,
                CustomFields = customFields
            };

            return await apiClient.SubscribeMemberAsync(mailingListId, member);
        }
    }
}

MoosendController.cs

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MyApp.WebApi.Services;

namespace MyApp.WebApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class MoosendController : ControllerBase
    {
        public MoosendController(IMoosendService moosendService)
        {
            MoosendService = moosendService;
        }

        private IMoosendService MoosendService { get; }

        [HttpPost("AddSubscriber/{name}/{email}/{dob}")]
        public async Task<Moosend.Api.Client.Common.Models.Subscriber> AddSubscriber(string name, string email,
            string dob)
        {
            return await MoosendService.AddSubscriberAsync(name, email, Convert.ToDateTime(dob));
        }
    }
}

Let’s now test it on Swaggwer, I’ll create 2 users with a birthdate before 01-01-2010 and one after this date, so we can test the segmentation properly:

Check now the Mailing List:

We can see our 3 members being added through the API, let’s take a look now at the segmentation:

We can see the two subscribers matching the segmentation criteria. In the next post I’ll be showing how to make use of the previously created mailing list, custom fields and segments with the campaigns and automation, we will also have a quick look at the template designer.

I hope you find it useful and keep tuned for more Moosend posts!