Sprout endless sites from a single Next.js and Umbraco instance
Tags: nextjs, javascript, webdev, tutorial
Published: 2022-07-14 14:50:16

Has building ordinary, single websites become monotonous for you? Do you need to let loose and just try something new for a bit? Well, you're in luck, we've got just the thing to spice up your developer life.

In this article, we will show you how to build multiple different websites using just a single Umbraco instance as the CMS and a single Next.js as the frontend. And to get that sweet, sweet performance, we're also going to use Enterspeed as a speed layer to serve our content.

![Is such a thing even possible? Yes it is!
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fx1a5b883n5pbm33y5a7.gif)

Some of you might sit with a puzzled look on your face, thinking: "Why on earth would one do that? Is this merely an insane idea like using [Google Sheets as a CMS](https://www.enterspeed.com/blog/using-google-sheets-as-your-cms)?".

Even though I'll admit that we love to push the envelope here at Enterspeed and try out crazy things, there are actually several use cases for this scenario. One of these scenarios is microsites. Microsites can, if used correctly, be an extremely powerful marketing strategy for your business.

_Pssst... Don't care about marketing and strategy but simply want the geeky stuff? 🤓 Then you can safely skip the section below._

## The power of Microsites 🐜
First of all, what the dickens is a microsite? 

A microsite is a small website that exists separate from the primary website, often on its own domain or subdomain. Unlike the primary website, it often has just a single goal, for instance, to promote a single product, service, or event.

Even though they share similarities, a microsite is not a landing page since a landing page is just a single page. A landing page is also focused mainly on hard conversions, whereas a microsite focus can be more about an experience, which can help to assist conversions later down the road.

Let's look at a few examples.

Say we are a business that owns several sub-brands or product lines. Each of these brands/products may have its own unique design and may also be targeting different demographics. This could for instance be a brewery selling both alcoholic and non-alcoholic beers.

Having a unique microsite, where they can fully control the visual identity and the communication, would not only make the message more powerful - it would also help with ad relevance when driving paid traffic to it.

Another similar example could be a brand with a single product that targets different demographics - this could be a nutrient supplement.

The target groups for this nutrient supplement could be:

- Older people who wish to reduce the risk of diseases like osteoporosis
- Athletes who are interested in gaining the extra performance
- Health-focused adults who are all about a healthy lifestyle

Just to name a few. How you market a product to these three groups differs highly - both in communication and design.

A third example could be a product or a service with an extremely long and complicated buyer's journey - for instance prefabricated houses. 

The time it takes for a family looking for a house to go from idea (top-funnel) to actual purchase (bottom-funnel) can be literal years.

This whole sales funnel can contain hundreds of touchpoints and consist of several questions like:

- "Rent or buy?"
- "How much money for a down payment?"
- "New house vs. old house"
- And much, much, much more...

Having strategic microsites, which assist along the whole buyer's journey can be their bytes worth in gold.

Last example, before jumping into the nitty-gritty details. Say you are a pest control company. You want to increase your organic presence in the search engines to gain more customers and reduce your reliance on SEM (paid advertisement in search engines).

A strategy here could be to build a microsite for each type of house pest, e.g. flies, bed bugs, wasps, rats, termites, etc.

Each of these sites will be highly relevant for this specific topic - and can be easier to build links to (some website owners prefer to link to a topic website instead of a company website).

Phew, that was a lot of marketing talk. To quote Linus Torvalds: _“Talk is cheap. Show me the code.”_

## Setting up your project
This example will be built around our fictional company - Enterbrew.


![Enterbrew microsites
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/awrge65rtqvp04rcbmxi.jpg)

Enterbrew is a brewery consisting of 4 subbrands targeting different demographics.

- Dev Beer
- ToBeer
- Lorem Beer
- Ice beer

Each of these microsites will have a homepage, a page listing the different beers, individual product pages for each beer, and finally an about page.

However, even though we're going to run all of this from the same backend and the same frontend, we're not limited to this structure. If we wanted, we could use different page types for each page.

Let's start by setting up a Umbraco instance.

## Setting up Umbraco
Start by [downloading](https://our.umbraco.com/download/) and installing Umbraco, e.g. on your local machine (You can also use [Umbraco Cloud](https://umbraco.com/products/umbraco-cloud/) if you prefer).

Note: _We're currently working on an Enterspeed-integration for version 10, for now, we're only supporting V7, V8, and V9._

Afterward, go to the Umbraco Backoffice, click on _Settings_ select _Document Types_. In this example we're using 4 document types:

- Home - the homepage which will also be our root node
- Beers - the parent page of all our beers
- Beer - the single product page
- Content page - used for general content - here our about page

We're not going to go over all the properties of each template, since they can be whatever you want. The important thing is that you set one of your document types, in our example, Home, as your root node. This is done under _Permissions_ in the template, where you set the _Allow as root_ to _true_.

Next, go to _Content_ and create a new item with the root node type, in our example, Home, you just created. Once it's been created, right-click on it and choose _Culture and Hostnames_. Add the domain you wish to use for this site, e.g. https://www.devbeer.com.

Repeat the step above for all the sites you wish to create.

Now it's time to set up the Enterspeed integration. Once we've connected our Umbraco site with our Enterspeed account, all of our content will be automatically synced to Enterspeed providing us with a blazing-fast speed layer, and essentially decoupling our Umbraco instance.

But before we start blasting content into Enterspeed, we first need to create a data source inside Enterspeed, which provides us with an API key.

![So anyway, I started blasting](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ctduajginap7e0kwvi31.jpg)
 
Go to Enterspeed, click on _Settings_ and select _Data sources_. Click _Create Group_ and give your group a name and a type. Next, add a data source (our Umbraco instance) and select an environment, e.g. _Development_. Click _Add_ and then click _Create_.

_Pssst... If you don't have an account already, you can create one right here 👉 [https://app.enterspeed.com/signup](https://app.enterspeed.com/signup) (no credit card required)._

Awesome, now we got an API key. Let's head back to our Umbraco instance.

Start by adding the Enterspeed integration via [NuGet](https://www.nuget.org/). We currently have integrations for versions [V7](https://www.nuget.org/packages/Enterspeed.Source.UmbracoCms.V7), [V8](https://www.nuget.org/packages/Enterspeed.Source.UmbracoCms.V8), and [V9](https://www.nuget.org/packages/Enterspeed.Source.UmbracoCms.V9/). 

Once you have installed the integration, you'll see a new tab under _Settings_ called _Enterspeed Settings_. Add _https://api.enterspeed.com_ as the Enterspeed endpoint and the API key you just created under _API key_.

Test your connection and click _Save configuration_ if everything goes well.

All content will now be synced to your Enterspeed account when you save and publish. If you need to seed already published content then click on _Content_ and select the _Enterspeed Content_ tab. Choose _Seed_ and click on the _Seed_-button.

Excellent - now it's time to set up Enterspeed.

## Setting up Enterspeed
Verify that all your content has been synced, by going to _Source entities_ under _Home_. Choose your data source and check if all your content is present. Furthermore, confirm that the domain you entered under _Culture and Hostnames_ in Umbraco matches the one in the URL column.

If everything is good, then it's time to create our first schemas. So first, what is a schema, and why do we need them?

One of the powerful things about Enterspeed is its ability to transform and model content into exactly the views you need. This is done by creating schemas. 

Schemas make it easy to combine multiple data sources, and to make deeply nested properties more accessible.

For our example, we will create a schema for each document type we created in Umbraco, and two for the navigation (one for single navigation items and one containing all the navigation items).

Once we have created and deployed the schemas, a view will be generated for each source entity that matches the schemas trigger. 

_💡 A trigger defines what you want the schema to "trigger on". It consists of a source group (your data source) and contains an array of one or more soure entity types (types of content)._

![Enterspeed schema: Page
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jj1jhvydpleekqxp19q8.png)

In the trigger property, we first define the source group (here our Umbraco data source group), and next the source entity types (e.g. our _page_ types).

Furthermore, we also need to define how we want to access these generated views when called from the front end. Here we have two options: Handle and URL. 

All the schemas that are based on document types from Umbraco reflect an actual page, therefore we will use the URL route option. 

The navigation on the other hand is an entity that will be used across the entire app, therefore we will use the Handle route option.

Let's take a look at each schema.

### Beer-schema

![Enterspeed schema: Beer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/907kpzszw2dy8vmfs3jb.png)
 
This schema gets its content from the _Beer_ source entity type and is routable via URL. The URL value will reflect the one in the source entity, which is identical to the page URL in Umbraco.

We have also set up some actions. The actions here will trigger a process of another schema. Since our _Beer_ is a child of _Beers_, which will show a list of all its children (_Beer_), we need a way to update the view whenever its children (_Beer_) change. This is done via the action type _process_.

In the _process_ action, we also need to provide an _originId_, which is the ID of the source entity given upon ingest (In Umbraco it reflects the page ID). We want this _originId_ to be the same as our current source entities' _(Beer) originParentId_ (also given upon ingest, here reflects the page's parent - _Beers_).

So to sum up actions. Every time one of our _Beer_ views changes, it triggers a processing of its parent, here the _Beers_ view.

Below we have _properties_, which is the actual content. Here we define a type, that we will use for rendering logic in our frontend and the URL to the page, which we will use to link to the individual beers on the overview page. Last, but not least, we grab all the content we have from Umbraco for this page, by using dynamic mapping. We group all of this under _content_ and map it all out by using: _"*": "p"_

### Beers-schema

![Enterspeed schema: Beers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8lp2fl1hbs5malc8vbum.png)

Just like the schema above, we have defined triggers, set route to URL, and specified _beers_ as the _type_ under properties.

Under _content_, there's suddenly a lot going on, and even though it might seem complex at first, it's actually quite simple when we go through it step by step.

Where we before had been working mapping out strings, we now want to output an array. So let's look at the structure.

First, we define the _type_ as an _array_. The reason why you don't have to do this on strings is simply syntactic sugar since strings are the most commonly used property type.

Next is the _input_ property. This is where you wish to retrieve the collection from. We use the _$lookup_ type to define a query-like criteria that the source entities have to match. 

The _operator_ we want is the equal operator and the _sourceEntityProperty_ we want to look at is _originParentId_. This should _matchValue_ of _originId_ (which here is the _Beers originId_).

So in other words. We want to source entities that have _Beers originId_ as their _OriginParentId_. All the children of _Beers_.

Cool. Now the last part - the content. The _items_ field is used to map the results.

The type we are using here is a _reference_ type, which is used to reference another schema - in this case the _Beer_ schema we created before. We reference another schema using the schemas' _alias_ - which happens to also be _beer_.

Lastly, we need to specify the ID of the source entity, which is found under _id_, and since we're looking for it "inside" one of the items, we prepend it with _item_, which then becomes: _item.id_

And voilà. We now have an array of all our beers 🍻

![Beers array (Postman screenshot)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0y7i9m015tpfahsgoay3.png)
 

### Home-schema

![Enterspeed schema: Home](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbv1tttpe8eu4j4vup9i.png)
 
And now to relax with a simple schema. We grab the _home_ source entity type, make it routable via URL, set the _type_ to _home_, and dynamically map all the content under _content_.

### Page-schema

![Enterspeed schema: Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i4utpog1mywcqq5496li.png)
 
This schema is structured the same way as the Home-schema.

### Navigation item-schema

![Enterspeed schema: Navigation item](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hwaw34dfryyskeh9h25m.png)
 
Now it's time to create our individual navigation items. First, we define which source entity types we want the navigation to consist of. In our example, we just want a very simple navigation, consisting of only parent pages (meaning not the individual beers). We also don't want it to include our homepage since we will use the logo for navigating back to it.

Unlike our previous schemas, we don't include a route, since this schema only will work as part of our navigation schema, and never will be called from the front end.

Under actions, we do the same as we did in the _Beer_ schema. We tell it to process its parent whenever something new happens to views with source entity type _page_ or _beers_. The parent in this case the site node, _home_. 

For properties, we grab the _metaData.name_ for the _label_, the _type_ we have given our other schemas, and the _url_ for _href_. From Umbraco, we also get a sortOrder, which we can use to sort our navigation in the front end.

### Navigation schema

![Enterspeed schema: Navigation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/waplcz6ccn3x2out7a8a.png)
 
Just like a [decorative oriental rug](https://www.youtube.com/watch?v=_vGK008c_rA), it's time to tie it all together. We use our root node, home, as our source entity type. 

In route, we will use _handles_, since the navigation is a component that will be fetched throughout the website, and not just a page. 

However, since we're building a multi-site solution, we need a way to differentiate the navigation for each site from the other. We do this by appending the handle, _navigation_, with _-{url}_, which inserts the URL for home, which is the root URL.

Inside _properties_, we create an array that, just like our _Beers_ schema, uses a reference to another schema, here _navigationItem_.

That was the last schema we needed to create. Now we just need to deploy our schemas so our brand new views can be generated.

Once you have deployed the schemas, you can navigate to _Generated views_ under _Home_ to see the result.

![Generated views in Enterspeed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdwakcgiume80po8kakj.png)
 
### Creating environment client(s)

The last thing we need to do is to create one or more environment clients.

_"Well, what is it, one or more?" you ask._

![Rick and Morty - *snap* "Yes"  meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgg40qmo7xqnsya94bht.gif)
 
Normally you have one environment client per website and, as the same suggests, per environment.

However, we are building a multisite based on a single Next.js instant, which makes it a little different.

If you want to use Enterspeeds [Routes API](https://docs.enterspeed.com/api#tag/Routes), for instance, to build a sitemap, then you need individual environment clients. If not, then you can do it with just a single environment client. So, the choice is yours. 

Don't worry, we will show you how you can support multiple environment clients later on.

To create an environment client, go to _Settings_ and select _Environment settings_.

If you have done everything correctly, then you should already see a list in the _Domains_ section of all the domains you have entered in Umbraco under _Culture and Hostnames_. If not, then you need to manually create them and their hostnames. But for now, let's assume you've done everything correctly - well done 🎉

![Domains Enterspeed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ebyolc1mdkexappa9nzl.png) 

Go to _Environment clients_ and click _Create_. Give your environment client a name and choose an environment. Then click Create. Next, choose all the domains you want to attach to this environment client. If you're planning on only using one, then select them all.

_Note: If you add more sites in the future, you need to go to edit the environment client and add these domains._

Now that we're ready to move on to the front end - our Next.js instance. Here we will also meet a very famous, red crustacean, which unfortunately is [not Zoidberg](https://knowyourmeme.com/memes/futurama-zoidberg-why-not-zoidberg).

![Sad Zoidberg](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy7cef4vxgmtyprcdwro.jpg)
 
## Setting up Next.js
Now it's time to set up our front end. For this, we're going to use Next.js, Express, and the secret sauce which makes it possible to use just a single Next.js instance, [Krabs](https://github.com/micheleriva/krabs).

What is Krabs? The author describes it as: _"...an enterprise-ready Express.js/Fastify middleware for serving thousands of different websites from a single Next.js instance."_

Pretty freaking cool - and with an awesome name nonetheless. 

![Krabs description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u5zly5nromxfjgqkxn0k.png)
 
Shout out to [Michele Riva](https://github.com/micheleriva) for this awesome package 👏

Note: _Krabs forces you to use a custom server. Therefore, deployments to Vercel are not supported. Krabs is also only supporting SSR at the moment, and not SSG nor ISR._

First things first - start by [creating your Next app](https://nextjs.org/docs/api-reference/create-next-app). Next, install [Krabs](https://github.com/micheleriva/krabs) and [Express](https://github.com/expressjs/express).

Note: _The steps for the Krabs configuration are borrowed from their [documentation](https://micheleriva.github.io/krabs/docs/tutorial-basics/getting-started)._

![Install Krabs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7jo336u1p8hxh1wxili.png)
 
Create a new file called server.js. This will be the entry point for our custom Express.js server, which handles our Next.js instance.

![Server.js file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5bwdj79nnsbqzffh82o7.png)
 
Next, create a file called .krabs.js (or .krabs.config.js) inside your project's root folder. Krabs will automatically import the configuration from this file.

We have extended the standard configuration by also adding a property called _enterspeedDomain_. The value for the _enterspeedDomain_ should be the same as the one given in Enterspeed (and Umbraco).

To actually see what we're doing, we need to configure our [hosts file](https://www.nublue.co.uk/guides/edit-hosts-file/). Insert all the domain names you want to view in your local environment.

![Hosts file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfj9hhviwzazzumgooao.png)
 
Awesome, now to create the "home" for each website. Inside your _pages_ directory, create a folder for each of your websites matching the name provided in the Krabs configuration file.

_Note: _app and _document pages are common to every website._

To test out that everything is working, you can create an index.js file with a simple "hello world"-function for each of the websites.

To see the result run: _node server.js_

If everything is working correctly, you should now be able to view each website using the URL you entered in your hosts file and Krabs configuration.

Next, let's create our connection to Enterspeed. Create a file called enterspeed.js 

![Enterspeed.js file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2nmc2vee2yzocmt3fjkm.png)
 
We have a call function and a function for each route type: _getByHandle_ and _getByUrl_. You will notice that we in the _getByUrl_ function have extended the response with _meta_, that way we can correctly handle 404-errors. Each of the functions calls our call function with a handle/url and a "tenant" (the website name configured in Krabs).

The reason why we include "tenant" is so we can differentiate between our websites and use a different environment client for each - if we choose. In our example, we have simply used the same. 

Next, create an __app.js_ file in the _pages_ directory. Here we will get the tenant name (website name), the _enterspeedDomain_, and also fetch our navigation.  

![_app.js file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/quhauqm7jx1gkyl27cms.png)
 
In our _App.getInitialProps_ you'll see we fetch the navigation via getByHandle and we add the _${enterspeedDomain}_ to the name, which matches the route name we created in our navigation schema in Enterspeed.

The _tenant_ and _enterspeedDomain_ are then added to the _pageProps_, which gets returned alongside the navigation. These are now available in our _App_ function.

In our _App_ function, you'll see we expand the tenant object to also include something called _tenantDetails_. This is simply some extra layout configuration we've added, which also could have been done in Umbraco. This is just to show how the front-end developer could do this if they didn't have access to the Umbraco instance.

![tenantDetails.js file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ffa1pmy1bn1wtuy45zpy.png)
 
We've chosen to add it in a separate file and not the Krabs configuration, so as to not mix layout specific into the Krabs configuration. 

Lastly, we wrap the App Component with a component called _MainLayout_, where we pass _tenant_ and _navigation_ as props. Let's take a look at that component.

We've made a simple layout, based on the awesome UI framework - [Chakra UI](https://chakra-ui.com/), but here's the neat part. We could have used a completely different UI framework/library for each of our sites - without hurting the main performance.

![Amazed man meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lit6vdhuz3ecdokzn9f6.jpg)
 
Yes, you read that correctly. Talking about bringing power and flexibility to the developer. 

No more internal battles between developers on which UI framework/library to use. No more trying to argue if Tailwind CSS is pure evil or not. Just like an [old Burger King commercial](https://www.youtube.com/watch?v=CJMsFGH4eoQ), everybody gets it their way.

You are now able to choose the UI framework/library that best fits the use case. 

We can achieve this by using Next.js' [Dynamic Import](https://nextjs.org/docs/advanced-features/dynamic-import). In our __app.js_ file, instead of using the _MainLayout_ component, we could have used a unique component for each website, based on the tenant name (website name). This could look like this:

![_app.js file with dynamic layout](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wrcpim0g7gcgqmhqaqdo.png)
 
Now, let's look at how we could set up the pages. We're going to use Next.js' Dynamic Routes - more specifically their [Optional catch all routes](https://nextjs.org/docs/routing/dynamic-routes#optional-catch-all-routes). This will allow us to catch all routes and match them with the corresponding view in Enterspeed.

Inside each of your website folders, create a file called [[...slug]].js

![Slug Js Optional Catch All Routes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gtdnfaw72qe20noh0tfm.png)
 
In our [getServerSideProps](http://getserversideprops/) function, we hook into its context and grab the _tenant_, _domain_, and _slug_. We pass these three variables on to the _getByUrl_ function we wrote in our _enterspeed.js_ file, which we set as our _data_ variable.

We then check to see if _data.status_ is equal to _404_, if so we change the _statusCode_ in our response object to 404 and set our _notFound_ variable to true.

The _data_ and the _notFound_ are then returned as props.

In our _Content_ component we check to see if _notFound_ is true, if so we use Next.js' [Error component](https://nextjs.org/docs/advanced-features/custom-error-page#reusing-the-built-in-error-page), if not we return our custom _Entity_ component.

![Entity.js file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/if1cukwmf7y3cutsh4vg.png)
 
Our Entity component is responsible for rendering the correct components, based on the type we defined in our schemas earlier.

You may be thinking: _"Hmm, but what if I want to have a custom layout for just one specific page? Should I then create a whole new schema with its own unique type?"_

Nope. Next.js got your back. Predefined routes always take precedence over dynamic routes.

This means if you want a unique layout for your Contact page, you simply create a page called "contact.js", and this will now respond to domain.com/contact.

Well, to round things up, let's look at how we would use our data to display a list of our beers.

All of the data has been passed to the view prop, so all that is left is to map over our array of beers and render them in a nice grid view, which will look like this:

![Devbeer.com - Beers page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9lzmle0c98gpklufam6u.png)
 
The code, which is based on Chakra UI, could look something like this:

![Beers.js file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mnfsyf1a8yr53dh7tj1n.png)
 
That's it. We hope you enjoyed the article and it provided some inspiration on how microsites can be used.

Cheers 🍻