Build your first React app
In this tutorial, you'll use the Create React appOpens in a new window boilerplate together with the JavaScript Delivery SDK to create a simple blog. You'll learn how to create react components to list articles, display article detail, resolve embedded content in rich text, and resolve links to content items. Let's get started.
You can view the final result hereOpens in a new window, see the source code on GithubOpens in a new window, or explore a live exampleOpens in a new window without installing anything.
New to Kentico Kontent?
If you're starting with Kentico Kontent, we suggest you begin with the Hello World tutorial, in which you'll learn the basics of creating content.
Table of contents
Requirements
To follow along, you'll need an IDE (such as Visual Studio CodeOpens in a new window) or the text editor of your choice. A basic knowledge of react and react-router comes handy but isn't required. Make sure you have Node and npmOpens in a new window installed and that you have a Kontent Sample Project to use.
Create a new project
1. Prepare the development environment
In your command line, run the following command to set up a new React project the create-react-app
Opens in a new window boilerplate.
npx create-react-app my-kontent-react-appnpx create-react-app my-kontent-react-app
2. Add dependencies
Navigate to the my-kontent-react-app
project folder and use npm
to install all the required libraries.
react-router-dom
– for client-side routing in a single page application@kentico/kontent-delivery
– for retrieving content from Kentico Kontentrxjs
– for managing asynchronous operations
cd my-kontent-react-app npm install --save react-router-dom rxjs @kentico/kontent-deliverycd my-kontent-react-app npm install --save react-router-dom rxjs @kentico/kontent-delivery
3. Create a config file with delivery client
In the src
folder, create a config.js
file and define the JavaScript SDK's DeliverClient
.
import {DeliveryClient} from "@kentico/kontent-delivery"; export const deliveryClient = new DeliveryClient({ // Tip: Use your own sample project ID instead of the Sample Project ID projectId: "975bf280-fd91-488c-994c-2f04416e5ee3", });import {DeliveryClient} from "@kentico/kontent-delivery"; export const deliveryClient = new DeliveryClient({ // Tip: Use your own sample project ID instead of the Sample Project ID projectId: "975bf280-fd91-488c-994c-2f04416e5ee3", });
You can paste your own Sample Project ID into the DeliveryClient
properties or keep using the default 975bf280-fd91-488c-994c-2f04416e5ee3
project ID. The default sample project contains everything required for this tutorial.
It's good practice to keep your DeliveryClient
configuration in a separate file and import it to your components and services.
4. (Optional) Simplify the looks
Overwrite contents of the App.css
with something simpler.
.App { padding: 0 20%; } .App-header { background-color: #222; height: 150px; padding: 20px; color: white; }.App { padding: 0 20%; } .App-header { background-color: #222; height: 150px; padding: 20px; color: white; }
5. Run your development environment
In your command line, use npm
to start a development server.
npm startnpm start
This opens http://localhost:3000/ in your browser.
The app will auto-reload every time you save a file to reflect the latest changes, you can keep it running as you code.
Add routing
In the src
folder, rewrite the App.js
file with the code below. This creates routes for the ArticleView
and ArticleListing
components, which you'll add in a moment.
import React from "react"; import {BrowserRouter as Router, Route} from "react-router-dom"; // You'll add these two Article components in a moment import ArticleListing from "./ArticleListing"; import ArticleView from "./ArticleView"; import "./App.css"; function App() { return ( <div className='App'> <header className='App-header'> <h1 className='App-title'>Our blog</h1> </header> {/* Specifies components to handle specific routes */} <Router> <div> {/* This line specifies that if the browser URL matches /, the ArticleListing component should be rendered in this place. */} <Route exact path='/' component={ArticleListing} /> {/* Paths can contain parameters, for example, path="/post/:slug", which are delivered to the target component through react props. */} <Route path='/post/:slug' component={ArticleView} /> </div> </Router> </div> ); } export default App;import React from "react"; import {BrowserRouter as Router, Route} from "react-router-dom"; // You'll add these two Article components in a moment import ArticleListing from "./ArticleListing"; import ArticleView from "./ArticleView"; import "./App.css"; function App() { return ( <div className='App'> <header className='App-header'> <h1 className='App-title'>Our blog</h1> </header> {/* Specifies components to handle specific routes */} <Router> <div> {/* This line specifies that if the browser URL matches /, the ArticleListing component should be rendered in this place. */} <Route exact path='/' component={ArticleListing} /> {/* Paths can contain parameters, for example, path="/post/:slug", which are delivered to the target component through react props. */} <Route path='/post/:slug' component={ArticleView} /> </div> </Router> </div> ); } export default App;
To make sure the new routes work, let's add the two components for displaying articles!
List articles
In the src
folder, create a new file named ArticleListing.js
. This component will retrieve all published articles in the project and render them as links.
Instead of requesting entire articles, use the elementsParameter
method to get only the elements you need. In this case, the title of the article and its URL.
import React, {useState, useEffect} from "react"; import {Link} from "react-router-dom"; import {deliveryClient } from "./config"; function ArticleListing() { // Uses the React state hook const [articles, setArticles] = useState([]); const [isLoading, setLoading] = useState(true); // Gets URL slugs and titles of all articles in the project const getArticles = () => { return deliveryClient .items() .type("article") .elementsParameter(["url_pattern", "title"]) .toObservable() .subscribe((response) => { setArticles(response.items); setLoading(false); }); }; useEffect(() => { const subscription = getArticles(); return () => subscription.unsubscribe(); }, []); // Shows loading until the app gets article from Kontent if (isLoading) { return <div>Loading...</div>; } // Displays a list of the retrieved articles with links to their detail return ( <ul> {articles.map((article) => { return ( <li key={article.url_pattern.value}> <Link to={`/post/${article.url_pattern.value}`}>{article.title.value}</Link> </li> ); })} </ul> ); } export default ArticleListing;import React, {useState, useEffect} from "react"; import {Link} from "react-router-dom"; import {deliveryClient } from "./config"; function ArticleListing() { // Uses the React state hook const [articles, setArticles] = useState([]); const [isLoading, setLoading] = useState(true); // Gets URL slugs and titles of all articles in the project const getArticles = () => { return deliveryClient .items() .type("article") .elementsParameter(["url_pattern", "title"]) .toObservable() .subscribe((response) => { setArticles(response.items); setLoading(false); }); }; useEffect(() => { const subscription = getArticles(); return () => subscription.unsubscribe(); }, []); // Shows loading until the app gets article from Kontent if (isLoading) { return <div>Loading...</div>; } // Displays a list of the retrieved articles with links to their detail return ( <ul> {articles.map((article) => { return ( <li key={article.url_pattern.value}> <Link to={`/post/${article.url_pattern.value}`}>{article.title.value}</Link> </li> ); })} </ul> ); } export default ArticleListing;

Your articles displayed in a list.
Now you'll add another component that takes care of displaying a specific article.
Display article detail
In the src
folder, create a new file named ArticleView.js
. This will retrieve a single article and render it on the page.
import React, {useState, useEffect} from "react"; import {Link} from "react-router-dom"; import {deliveryClient } from "./config"; function ArticleView({match, history}) { // Uses the react state hook const [article, setArticle] = useState({}); const [isLoading, setLoading] = useState(true); // Gets an article by its URL slug const getArticle = (slug) => { return deliveryClient .items() .type("article") .equalsFilter("elements.url_pattern", slug) .toObservable() .subscribe((response) => { setArticle(response.items[0]); setLoading(false); }); }; useEffect(() => { const subscription = getArticle(match.params.slug); return () => subscription.unsubscribe(); }, [match.params.slug]); // Shows loading until the app gets article from Kontent if (isLoading) { return <div>Loading...</div>; } return ( <div> <Link to='/'>Home</Link> <h1>{article.title.value}</h1> <div className='article_body' dangerouslySetInnerHTML={{__html: article.body_copy.resolveHtml()}} /> </div> ); } export default ArticleView;import React, {useState, useEffect} from "react"; import {Link} from "react-router-dom"; import {deliveryClient } from "./config"; function ArticleView({match, history}) { // Uses the react state hook const [article, setArticle] = useState({}); const [isLoading, setLoading] = useState(true); // Gets an article by its URL slug const getArticle = (slug) => { return deliveryClient .items() .type("article") .equalsFilter("elements.url_pattern", slug) .toObservable() .subscribe((response) => { setArticle(response.items[0]); setLoading(false); }); }; useEffect(() => { const subscription = getArticle(match.params.slug); return () => subscription.unsubscribe(); }, [match.params.slug]); // Shows loading until the app gets article from Kontent if (isLoading) { return <div>Loading...</div>; } return ( <div> <Link to='/'>Home</Link> <h1>{article.title.value}</h1> <div className='article_body' dangerouslySetInnerHTML={{__html: article.body_copy.resolveHtml()}} /> </div> ); } export default ArticleView;
When you check your app in the browser and click on an article, the app will display the article's content.

Detail of a specific article.
Render rich text content
Kontent gives you a great amount of flexibility for embedding pieces of content in rich text elements such as videos, tweets, or code examples.
Here is an example of how inserting videos works in a nutshell:
- You create a Hosted video content type. This defines information about the video your application needs.
- In articles, you insert a Hosted video component (or item) into a rich text element.
- Your application resolves the rich text component (or item) to the appropriate HTML and renders it.
The Sample Project already contains the Hosted video content type.

A Hosted video component used in a rich text element in one of the articles in Kontent.
This tutorial assumes you've already defined a video content type and used it in an article. If you don't know how to do that yet, check out the basics of structured content.
1. Define a resolver for the video
Use a content item resolver to define how to render items and components of a specific type in rich text elements.
- A content resolver accepts a content item object as its argument and returns the desired HTML output as a string.
- It's good practice to define your resolvers in a separate file and import them to your components.
In the src
folder, create a new file named itemResolver.js
.
export const resolveItemInRichText = (item) => { if (item.system.type === "hosted_video") { let video = item; if (video.video_host.value.find((item) => item.codename === "vimeo")) { return `<iframe class="hosted-video__wrapper" src="https://player.vimeo.com/video/${video.video_id.value}?title=0&byline=0&portrait=0" width="100%" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen > </iframe>`; } else if (video.video_host.value.find((item) => item.codename === "youtube")) { return `<iframe class="hosted-video__wrapper" width="100%" height="360" src="https://www.youtube.com/embed/${video.video_id.value}" frameborder="0" allowfullscreen > </iframe>`; } } return undefined; };export const resolveItemInRichText = (item) => { if (item.system.type === "hosted_video") { let video = item; if (video.video_host.value.find((item) => item.codename === "vimeo")) { return `<iframe class="hosted-video__wrapper" src="https://player.vimeo.com/video/${video.video_id.value}?title=0&byline=0&portrait=0" width="100%" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen > </iframe>`; } else if (video.video_host.value.find((item) => item.codename === "youtube")) { return `<iframe class="hosted-video__wrapper" width="100%" height="360" src="https://www.youtube.com/embed/${video.video_id.value}" frameborder="0" allowfullscreen > </iframe>`; } } return undefined; };
In a real-world app, you'd define a type resolver for each content type that your editors can use for creating content.
2. Register the resolver
In the src
folder, modify the ArticleView.js
component.
- Import your
resolveItemInRichText
resolver. - Add a
.queryConfig
method to your delivery client query to register the resolver.
// Imports your new content item resolver. import {resolveItemInRichText} from "./itemResolver"; ... function ArticleView({match, history}) { ... const getArticle = (slug) => { return deliveryClient .items() .type("article") ... // Registers your new resolver. .queryConfig({ richTextResolver: resolveItemInRichText }) ... };// Imports your new content item resolver. import {resolveItemInRichText} from "./itemResolver"; ... function ArticleView({match, history}) { ... const getArticle = (slug) => { return deliveryClient .items() .type("article") ... // Registers your new resolver. .queryConfig({ richTextResolver: resolveItemInRichText }) ... };
Notice that you've added the resolver to a single query. If you don't want to do this each time, define resolvers globallyOpens in a new window.

Your app can now display videos inserted into articles. Try http://localhost:3000/post/coffee-beverages-explainedOpens in a new window.
Resolve links to content items
Textual links to content items in rich text elements are, by default, returned as <a>
tags without any href
value. This tells you to which item the link points but you need to specify the link destination yourself.
<a data-item-id="23f71096-fa89-4f59-a3f9-970e970944ec" href="">this is a link to another article</a><a data-item-id="23f71096-fa89-4f59-a3f9-970e970944ec" href="">this is a link to another article</a>
1. Create a link resolver
Use a link resolver to define how to render links to content items of a specific content type. A link resolver accepts a rich text link object as its argument and returns the desired relative URL as a string.
It's good practice to define your resolvers in a separate file and import them to your components.
In the src
folder, create a file named linkResolver.js
.
export const resolveContentLink = (link) => { if (link.type === "article") { return {url: `/post/${link.urlSlug}`}; } return undefined; };export const resolveContentLink = (link) => { if (link.type === "article") { return {url: `/post/${link.urlSlug}`}; } return undefined; };
The resolver here is short on purpose. In a real app, you would define a link resolver for each type of content your editors can link to in rich text.
2. Register your link resolver
In the src
folder, modify the ArticleView.js
component.
- Import your
resolveContentLink
resolver. - Add a
.queryConfig
method to your delivery client query to register the resolver.
// Imports your new link resolver. import {resolveContentLink} from "./linkResolver"; ... function ArticleView({match, history}) { ... const getArticle = (slug) => { return deliveryClient .items() .type("article") ... // Registers your new resolver. .queryConfig({ urlSlugResolver: resolveContentLink }) ... };// Imports your new link resolver. import {resolveContentLink} from "./linkResolver"; ... function ArticleView({match, history}) { ... const getArticle = (slug) => { return deliveryClient .items() .type("article") ... // Registers your new resolver. .queryConfig({ urlSlugResolver: resolveContentLink }) ... };
Notice that you've added the resolver to a single query. If you don't want to do this each time, define resolvers globallyOpens in a new window.
Clicking on links to other Article content items now takes you to the linked article. But the app reloads completely every time you click a link.
Because you can't use the react-router's <Link>
components in your resolvers, you need to write a handler method that will navigate to the linked article using react-router-dom
programmatically.
3. Add a click event handler
Add an onClick
handler to the div
rendering the HTML of the rich text element. You need to pass the event
object and the bodyCopy
rich text element of the article containing metadata about all links to content items.
In the src
folder, modify the return method in your ArticleView.js
.
return ( <div> <Link to="/">Home</Link> <h1>{article.title.value}</h1> <div className='article_body' dangerouslySetInnerHTML={{ __html: article.body_copy.resolveHtml() }} // Registers your custom onClick handler onClick={(event) => handleClick(event, article.body_copy)} /> </div> );return ( <div> <Link to="/">Home</Link> <h1>{article.title.value}</h1> <div className='article_body' dangerouslySetInnerHTML={{ __html: article.body_copy.resolveHtml() }} // Registers your custom onClick handler onClick={(event) => handleClick(event, article.body_copy)} /> </div> );
4. Resolve and navigate to the new route
In the same file, add a new handleClick
method to the ArticleView
function. This is what the method does:
- Checks if the user clicked on a link to a content item, other links should behave normally. In such case it prevents the browser from handling the event with
event.preventDefault();
. - Handles the event by constructing a new path with the imported
resolveContentLink
method. - Pushes the new path into the
history
object to navigate to the new URL. This passes new props to theViewArticle
component.
const handleClick = (event, richTextElement) => { // Checks if the user clicked on a link to a content item. if (event.target.tagName === 'A' && event.target.hasAttribute('data-item-id')) { event.preventDefault(); const id = event.target.getAttribute('data-item-id'); const link = richTextElement.links.find(link => link.linkId === id); const newPath = resolveContentLink(link).url; if (newPath) history.push(newPath); } }const handleClick = (event, richTextElement) => { // Checks if the user clicked on a link to a content item. if (event.target.tagName === 'A' && event.target.hasAttribute('data-item-id')) { event.preventDefault(); const id = event.target.getAttribute('data-item-id'); const link = richTextElement.links.find(link => link.linkId === id); const newPath = resolveContentLink(link).url; if (newPath) history.push(newPath); } }
Consider how you want to route
There is more that one way to navigate to a new route programmatically and this article on React RouterOpens in a new window does a good job explaining the pros and cons of each approach.
It also explains how to use withRouter
if your component is not rendered by Router
and can't access history
through its props.
Build and deploy
Because your application doesn't require any server-side code, you can host it as a collection of static files for free on Surge, Github pages, or a similar service.
Run the following commands to build and deploy your app using Surge.
- Before deploying, you need to rename the built
index.html
file to200.html
to enable client-side routingOpens in a new window. - The first time you run Surge, it will prompt you to set up your account. Every other time it will confirm the build directory and generate a funny subdomain for your app.
- You can store the set of commands for deploying your app as a script inside your
package.json
file.
npm install --global surge # Build your app npm run build # Rename index.html to 200.html in the build folder mv build/index.html build/200.html # Run surge to upload your built app surge buildnpm install --global surge # Build your app npm run build # Rename index.html to 200.html in the build folder mv build/index.html build/200.html # Run surge to upload your built app surge build
{ // ... "scripts": { "deploy": "npm run build && mv build/index.html build/200.html && surge build <your domain>.surge.sh" // ... }{ // ... "scripts": { "deploy": "npm run build && mv build/index.html build/200.html && surge build <your domain>.surge.sh" // ... }
Conclusion
Congratulations! Your app can retrieve and display content from Kentico Kontent, change routes, resolve links between content items and display embedded videos. View the final source code on GithubOpens in a new window.
For learning purposes, we have simplified some aspects of the application in this tutorial. For example, we recommend defining strongly-typed models for your project's content types using a model generatorOpens in a new window.