Previewing content

Run your app in two environments to give content contributors a way to preview their new content.

In this article, you'll learn about a general approach to running your apps in two environments and using the preview environment for content preview in Kentico Cloud. This will enable your content contributors to test how new versions of content fit within the rest of your app.

Deploying changes to content

Deploying changes to content is a common task for developers and content contributors alike.

To deploy the changes, you need a single Kentico Cloud project with the Delivery API and Delivery Preview API. The project stores all your content, the Delivery API returns the published versions of content items, while the Delivery Preview API returns the latest versions.

Once set up, your content contributors can preview unfinished content items in your web app or on other platforms, and look at their content in the context of the app.

Content workflow

When content contributors create new content items (such as articles) in the project, the content items begin their content workflow in the Draft step.

New content item at the beginning of its workflow in the content list.

New content item at the beginning of its workflow in the content list.

Right after being created, the new content items become available via the Delivery Preview API.

The following example shows how you can get the latest version of the "On Roasts" article, as shown in the image above.

curl --request GET \
  --url https://preview-deliver.kenticocloud.com/<YOUR_PROJECT_ID>/items/on_roasts \
  --header 'authorization: Bearer <YOUR_PREVIEW_API_KEY>'
using KenticoCloud.Delivery;

// Initializes a content delivery client for previewing content
IDeliveryClient client = DeliveryClientBuilder
    .WithOptions(builder => builder
        .WithProjectId("975bf280-fd91-488c-994c-2f04416e5ee3")
        .UsePreviewApi("<YOUR_PREVIEW_API_KEY>")
        .Build())
    .Build();

// Gets the latest version of a content item
// Tip: Generate strongly typed models via https://github.com/Kentico/cloud-generators-net
DeliveryItemResponse<object> response = await client.GetItemAsync<object>("on_roasts");

var item = response.Item;
const KenticoCloud = require('kentico-cloud-delivery');

// Create strongly typed models according to https://github.com/Kentico/kentico-cloud-js/blob/master/doc/delivery.md#creating-models
class Article extends KenticoCloud.ContentItem {
    constructor() {
        super();
    }
}

const deliveryClient = new KenticoCloud.DeliveryClient({
    projectId: '<YOUR_PROJECT_ID>',
    enablePreviewMode: true,
    previewApiKey: "<YOUR_PREVIEW_API_KEY>",
    typeResolvers: [
        new KenticoCloud.TypeResolver('article', () => new Article)
    ]
});

deliveryClient.item('on_roasts')
    .getObservable()
    .subscribe(response => console.log(response));
import { ContentItem, DeliveryClient, Fields, TypeResolver } from 'kentico-cloud-delivery';

// Create strongly typed models according to https://github.com/Kentico/kentico-cloud-js/blob/master/doc/delivery.md#creating-models
export class Article extends ContentItem {
    public title: Fields.TextField;
    public summary: Fields.TextField;
    public post_date: Fields.DateTimeField;
    public teaser_image: Fields.AssetsField;
    public related_articles: Article[];
}

const deliveryClient = new DeliveryClient({
    projectId: '<YOUR_PROJECT_ID>',
    enablePreviewMode: true,
    previewApiKey: "<YOUR_PREVIEW_API_KEY>",
    typeResolvers: [
        new TypeResolver('article', () => new Article)
    ]
});

deliveryClient.item<Article>('on_roasts')
    .getObservable()
    .subscribe(response => console.log(response));
import com.kenticocloud.delivery;

DeliveryClient client = new DeliveryClient("<YOUR_PROJECT_ID>", "<YOUR_PREVIEW_API_KEY>");

ContentItem item = client.getItem("on_roasts").item;
import com.kenticocloud.delivery_core.*;
import com.kenticocloud.delivery_rx.*;

import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;

// Prepares an array to hold strongly-typed models
List<TypeResolver<?>> typeResolvers = new ArrayList<>();

// Registers the type resolver for articles
typeResolvers.add(new TypeResolver<>(Article.TYPE, new Function<Void, Article>() {
    @Override
    public Article apply(Void input) {
        return new Article();
    }
}));

// Prepares the DeliveryService configuration object
String projectId = "<YOUR_PROJECT_ID>";
String previewApiKey = "<YOUR_PREVIEW_API_KEY>";
IDeliveryConfig config = DeliveryConfig.newConfig(projectId)
  	.withTypeResolvers(typeResolvers)
    .withPreviewApiKey(previewApiKey);

// Initializes a DeliveryService for Java projects
IDeliveryService deliveryService = new DeliveryService(config);

// Gets the latest version of a content item using a simple request
Article item = deliveryService.<Article>item("on_roasts")
    .get()
    .getItem();

// Gets the latest version of a content item using RxJava2
deliveryService.<Article>item("on_roasts")
    .getObservable()
    .subscribe(new Observer<DeliveryItemResponse<Article>>() {
        @Override
        public void onSubscribe(Disposable d) {
        }

        @Override
        public void onNext(DeliveryItemResponse<Article> response) {
            // Gets the article
            Article item = response.getItem();
        }

        @Override
        public void onError(Throwable e) {
        }

        @Override
        public void onComplete() {
        }
    });
<?php

// Defined by Composer to include required libraries
require __DIR__ . '/vendor/autoload.php';

use KenticoCloud\Delivery\DeliveryClient;

$client = new DeliveryClient('<YOUR_PROJECT_ID>', '<YOUR_PREVIEW_API_KEY>');

$item = $client->getItem('on_roasts');
import KenticoCloud

let client = DeliveryClient.init(projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_PREVIEW_API_KEY>")

// More about strongly-typed models https://github.com/Kentico/cloud-sdk-swift#using-strongly-typed-models
client.getItem(modelType: Article.self, itemName: "on_roasts") { (isSuccess, itemResponse, error) in
    if isSuccess {
        if let article = itemResponse.item {
            // Use your item here
        }
    } else {
        if let error = error {
            print(error)
        }
    }
}

As more people work on content, the content items progress through their workflow and, when all requirements are met, can be scheduled for publishing or published right away. Published items then become available via the Delivery API.

Preview and live environments

To preview unpublished content in your app, we recommend running the app in its own environment separated from the live version of the app.

The main difference is that the app running in the live environment uses the Delivery API, while the app in the preview environment uses the Delivery Preview API. To differentiate between the environments in your app, you can, for instance, use environment variables. The way to check the environment variables will depend on the service you use to deploy your apps.

For example, when using one of our Delivery SDKs, the API to use is often determined by providing or omitting the Preview API key. Note that server-side technology (such as ASP.NET MVC, Node.js with the Express.js framework, or others) is required to keep the Preview API key secret.

In practice, your application can check whether it's run in a preview environment, set a boolean flag based on the check, and determine whether to use the Delivery Preview API.

using KenticoCloud.Delivery;

// TODO: Determine whether the app is running in a preview environment
bool isPreview = ...;

// Prepares connection options for the content delivery client
DeliveryOptions options = new DeliveryOptions()
{
    ProjectId = "975bf280-fd91-488c-994c-2f04416e5ee3",
    PreviewApiKey = "<YOUR_PREVIEW_API_KEY>",
    // Determines whether to use the Delivery Preview API
    UsePreviewApi = isPreview
};

// Ensures the specified options configuration is valid
options.Validate();

// Initializes a content delivery client
IDeliveryClient client = DeliveryClientBuilder
    .WithOptions(_ => options)
    .Build();
const KenticoCloud = require('kentico-cloud-delivery');

// TODO: Determine whether the app is running in a preview environment
const isPreview = ...;

const deliveryClient = new KenticoCloud.DeliveryClient({
    projectId: '<YOUR_PROJECT_ID',
    enablePreviewMode: isPreview,
    previewApiKey: "<YOUR_PREVIEW_API_KEY>",
    typeResolvers: []
});
import { DeliveryClient } from 'kentico-cloud-delivery';

// TODO: Determine whether the app is running in a preview environment
const isPreview = ...;

const deliveryClient = new DeliveryClient({
    projectId: '<YOUR_PROJECT_ID',
    enablePreviewMode: isPreview,
    previewApiKey: "<YOUR_PREVIEW_API_KEY>",
    typeResolvers: []
});
import com.kenticocloud.delivery;

// TODO: Determine whether the app is running in a preview environment
boolean isPreview = ...;

DeliveryOptions deliveryOptions = new DeliveryOptions();
deliveryOptions.setProjectId("<YOUR_PROJECT_ID>");
deliveryOptions.setPreviewApiKey("<YOUR_PREVIEW_API_KEY>");
deliveryOptions.setUsePreviewApi(isPreview ? true : false);

DeliveryClient client = new DeliveryClient(deliveryOptions);
import com.kenticocloud.delivery_core.*;
import com.kenticocloud.delivery_rx.*;

import io.reactivex.functions.Function;

// TODO: Determine whether the app is running in a preview environment
boolean isPreview = ...;

// Prepares the DeliveryService configuration object
String projectId = "<YOUR_PROJECT_ID>";
String previewApiKey = "<YOUR_PREVIEW_API_KEY>";
IDeliveryConfig config = DeliveryConfig.newConfig(projectId)
    .withTypeResolvers(typeResolvers)
    .withPreviewApiKey(isPreview ? previewApiKey : null);

// Initializes a DeliveryService for Java projects
IDeliveryService deliveryService = new DeliveryService(config);
<?php

// Defined by Composer to include required libraries
require __DIR__ . '/vendor/autoload.php';

use KenticoCloud\Delivery\DeliveryClient;

// TODO: Determine whether the app is running in a preview environment
$isPreview = ...;

$client = new DeliveryClient("<YOUR_PROJECT_ID>", $isPreview ? "<YOUR_PREVIEW_API_KEY>" : null);
import KenticoCloud

// TODO: Determine whether the app is running in a preview environment
let isPreview = ...

let previewKey = isPreview ? "<YOUR_PREVIEW_API_KEY>" : nil

let client = DeliveryClient.init(projectId: "<YOUR_PROJECT_ID>", apiKey: previewKey)

Content preview in Kentico Cloud

With your application running in two separate environments, you can add preview URLs for the content types in your project and preview content that is not yet published.

Tip: See how to configure previews inside Kentico Cloud.

Configuring preview URLs

To set up preview URLs for your project:

  1. In Kentico Cloud, choose Project settings from the app menu.
  2. Under Development, choose Preview URLs.
  3. Type in the URLs to your app in the preview environment.

The URLs must be absolute, containing protocol, domain, and path, in the following form protocol://domain/path.

A static preview URL for a homepage of an app running at https://preview.myapp.com can be, for example, https://preview.myapp.com/.

With more dynamic pages, such as articles translated to multiple languages, we recommend using macros when constructing the preview URLs. There are 2 macros available:

  • {URLslug} – resolves to the value of a URL slug element in a content item.
  • {Lang} – resolves to the codename of the currently selected language in your Kentico Cloud project.

When you combine the macros, you can create a preview URL such as https://preview.myapp.com/{Lang}/articles/{URLslug} that can resolve to, for example, https://preview.myapp.com/es-es/articles/en-asados.

Example: Using macros in preview URLs.

Example: Using macros in preview URLs.

Once you set up the URLs, your content contributors can use a preview button () when editing new versions of content items. Note that the button is not shown for already published content items.

Clicking the preview button (:fa-eye:) in content editing interface opens the address specified in the preview URL.

Clicking the preview button () in content editing interface opens the address specified in the preview URL.

Changing project structure

If you change any of the project models (this includes content types, sitemap, and taxonomy groups), the changes are immediately reflected in the content items.

This means that changes to the project structure cannot be separately tested in preview environment because they directly affect the live environment.

Continuous development

Deploying changes in the structure of a project to another project in order to, for example, test new content layouts, currently cannot be done.

We're working on bringing the support for continuous development to Kentico Cloud in the near future. See our Roadmap to learn more.

What's next?

In this article, you've learned how to deploy changes to content in your Kentico Cloud project. Your content contributors can now preview their new content in a separate preview environment before it's published.