How to Handle Previews in a Headless Architecture
Tags: strapi, javascript, headlesscms
Published: 2022-08-30 07:49:00

_In this tutorial, you’ll learn to implement a preview system when working with a headless CMS like Strapi._

**Author: Mark Munyaka**

There is an ongoing shift in content management from traditional CMS to headless CMS. [A headless CMS](https://jamstack.org/headless-cms/) allows you to completely separate your content management system from the presentation layer. The content is made available via API and can be consumed in any kind of frontend, from websites to mobile apps.

Using headless CMS has opened up a new way of building websites, known as pre-rendering. It is one of the best-known techniques in Jamstack, in which the website is compiled into a set of static assets like pre-built HTML, CSS, and JavaScript files with the help of a [static site generator (SSG)](https://jamstack.org/generators/). During the build time, the files are created by collecting the data from a headless CMS. These files are cached to a content delivery network (CDN) and served to a user on each request from the nearest CDN node. This improves speed and response times and reduces hosting costs.

However, content creators need to preview their content before publishing it to production, meaning they need to wait for an entire build to complete before they can view their content. To solve this problem, a [preview mode](https://nextjs.org/docs/advanced-features/preview-mode) allows editors to view their changes on the fly.

In this tutorial, you’ll learn to implement a preview system when working with a headless CMS like [Strapi](https://strapi.io/). You’ll implement the frontend in [Next.js](https://nextjs.org/) for creating content previews.

## Prerequisites
Here are what you’ll need to get started:

- Node.js: This tutorial uses Node v16.1.x
- Strapi: This tutorial uses Strapi v4.2.x
- Next.js: This tutorial uses Next.js v12.2.x

## Setting up the Project
You’ll need a master directory that holds the code for both the frontend (Next.js) and backend (Strapi). Open your terminal, navigate to a path of your choice, and create a project directory by running the following command:

```bash
    mkdir strapi-nextjs-previews
```
In the `strapi-nextjs-previews` directory, you’ll install both Strapi and Next.js projects.

## Setting up Strapi

In your terminal, execute the following command to create the Strapi project:

```bash
    npx create-strapi-app backend  --quickstart --template @strapi/template-blog@1.0.0 blog
```

This creates a directory named `backend` and contains all the code related to your Strapi project. It uses the [preconfigured Strapi template for blogs](https://github.com/strapi/starters-and-templates/tree/main/packages/templates/blog).

Your Strapi project will start on port 1337 and open [localhost:1337/admin/auth/register-admin](http://localhost:1337/admin/auth/register-admin) in your browser. Set up your administrative user:

![Administrative user signup form for Strapi](https://i.imgur.com/XCVJy0Q.png)


Enter your details and click the **Let’s Start** button.

You’ll be taken to the Strapi dashboard. Select the **Content Manager** header on the left sidebar, then, click the **Article** tab under the **COLLECTION TYPES** menu for pre-written articles you can access.

![List of articles in Strapi CMS](https://paper-attachments.dropbox.com/s_78E48E438BAD42C0DC175A0956096BD58B7F69A6652A796D6969EC2C75E7774D_1658910467115_list-of-articles-in-strapi-cms.png)


All the articles in your Strapi CMS are in the published state. Since the goal is to view the unpublished content, move some of the articles to the draft state.

Click the blog with id "1". You’ll be taken to the edit view screen:

![Content Manager edit view in Strapi CMS](https://paper-attachments.dropbox.com/s_78E48E438BAD42C0DC175A0956096BD58B7F69A6652A796D6969EC2C75E7774D_1658910727063_content-manager-edit-view-in-strapi-cms.png)

Click the **Unpublish** button at the top of the screen to change the article into a draft.

![A draft entry in Strapi CMS](https://paper-attachments.dropbox.com/s_78E48E438BAD42C0DC175A0956096BD58B7F69A6652A796D6969EC2C75E7774D_1658910664991_a-draft-entry-in-strapi-cms.png)

Now, you have five published articles and one draft article. Next, you’ll learn how to fetch these articles via Strapi API and display them on a Next.js website.

## Setting up Next.js

It’s time to build the frontend website using Next.js. Since your current terminal window is serving the Strapi project, open another terminal window and execute the following command to create a Next.js project:

```bash
    npx create-next-app frontend
```

This creates a directory named `frontend` containing all the code related to the Next.js project. Navigate into the directory and start the Next.js development server by running the following commands:

```bash
    cd frontend
    npm run dev
```

This will start the development server on port 3000 and take you to [localhost:3000](http://localhost:3000/). The first view of the Next.js website will look like this:

![First view of a Next.js website](https://i.imgur.com/XwkGU0j.png)

## Setting up the UI Framework
Now, add Bootstrap to style your blog. You can actually choose any design framework, but the installation steps may vary.

Shut down the Next.js development server by pressing **Ctrl-C** in your terminal and execute the following command to install [bootstrap](https://www.npmjs.com/package/bootstrap), [react-bootstrap](https://www.npmjs.com/package/react-bootstrap), and [node-sass](https://www.npmjs.com/package/node-sass) NPM packages for your Next.js website:

```bash
    npm install bootstrap react-bootstrap node-sass --save
```

Once the installation is complete, open the `next.config.js` file and add the following code:

```js
    const path = require('path');
    
    module.exports = {
      reactStrictMode: true,
      sassOptions: {
        includePaths: [path.join(__dirname, 'styles')],
      },
    };
```

This tells the node-sass to compile the SCSS to CSS from the `styles` directory.
In the `styles` directory:

1. Rename the `globals.css` to `globals.scss`.
2. Replace the existing styles in `globals.scss` with the following line of code:

```css
    @import '/node_modules/bootstrap/scss/bootstrap.scss';
```
In the `pages` directory, open the `_app.js` file and replace the existing code with the following:

```js
    import '/styles/globals.scss';
    
    function MyApp({ Component, pageProps }) {
      return <Component {...pageProps} />;
    }
    
    export default MyApp;
```
The above steps will complete the Bootstrap setup in your Next.js website.

You can learn more about [customizing the Bootstrap in Next.js here](https://www.ravsam.in/blog/setup-and-customize-bootstrap-in-nextjs/).

## Setting up the Markdown to HTML Converter
In the Strapi CMS, the article content is written in Markdown, so you need a way to convert Markdown into HTML. For this, you can use the [react-showdown](https://www.npmjs.com/package/react-showdown) NPM package.

To install react-showdown, execute the following command in your terminal:
```bash
    npm install react-showdown --save
```
## Designing Articles Page

Now that you have set up the necessary packages for developing your Next.js website, you need to design an `articles` page. It will fetch your articles from Strapi CMS and display them in the UI. In the `pages` directory, create an `articles` directory by running the following command:

```bash
    mkdir pages/articles
```

In the `articles` directory, create an `index.js` file and add the following code:
```js
    import { Container, Row, Col } from 'react-bootstrap';
    import { fetchArticlesApi } from '/lib/articles';
    import Link from 'next/link';
    
    const ArticlesView = (props) => {
      // 2
      const { articles } = props;
      // 3
      return (
        <section className="py-5">
          <Container>
            <Row>
              <Col lg="7" className="mx-lg-auto">
                <h1 className="mb-5 border-bottom">Articles</h1>
                {articles.data.map((article) => {
                  return (
                    <div key={article.id} className="mb-4">
                      <h2 className="h5">{article.attributes.title}</h2>
                      <p className="mb-2">{article.attributes.description}</p>
                      <Link href={'/articles/' + article.attributes.slug}>
                        <a className="">Read More</a>
                      </Link>
                    </div>
                  );
                })}
              </Col>
            </Row>
          </Container>
        </section>
      );
    };
    
    export async function getStaticProps() {
      // 1
      const articles = await fetchArticlesApi();
      return {
        props: { articles },
      };
    }
    
    export default ArticlesView;
```

In the above code:
1. You use the `fetchArticlesApi` function in the `getStaticProps` function provided by Next.js. In the `getStaticProps` function, you fetch the articles from the Strapi CMS and return them as a prop.
2. The `articles` array is destructured from the `prop` variable in the `ArticlesView`.
3. The `articles` are displayed in the UI.

The next step is to write code to fetch articles using Strapi API. For that, you need to implement the `fetchArticlesApi` function.

Create the `lib` directory under the `frontend` directory by running the following command:
```bash
    mkdir lib
```
In the `lib` directory, create an `articles.js` file and add the following code:

```js
    const STRAPI_URL = process.env.STRAPI_URL;
    
    // Helper function to GET the articles from Strapi
    async function fetchArticlesApi() {
      const requestUrl = `${STRAPI_URL}/articles`;
      const response = await fetch(requestUrl);
      return await response.json();
    }
    
    // Helper function to GET a single article from Strapi
    async function fetchArticleApi(slug) {
      const requestUrl = `${STRAPI_URL}/articles/?filters\[slug\][$eq]=${slug}`;
      const response = await fetch(requestUrl);
      return await response.json();
    }
    
    // Helper function to GET a single article from Strapi which is in draft state
    async function fetchArticlePreviewApi(slug) {
      const requestUrl = `${STRAPI_URL}/articles?publicationState=preview&filters\[slug\][$eq]=${slug}`;
      const response = await fetch(requestUrl);
      return (await response.json())[0];
    }
    
    export { fetchArticlesApi, fetchArticleApi, fetchArticlePreviewApi };
```
In the above code, you have declared two functions:

1. The `fetchArticlesApi` function is used to fetch the published articles from the Strapi articles’ endpoint, [localhost:1337/api/articles](http://localhost:1337/api/articles).
2. The `fetchArticleApi` function is used to fetch a single article from Strapi based on the `slug` passed as a parameter. It calls the Strapi single article endpoint, i.e. [localhost:1337/api/articles/{slug}](http://localhost:1337/api/articles/%7Bslug%7D), and returns the fetched article.
3. The `fetchArticlePreviewApi` function is used to fetch a single article when the preview mode is enabled. It calls the Strapi articles’ endpoint, ie [localhost:1337/api/articles](http://localhost:1337/api/articles), and passes a query parameter `publicationState` whose value is set to `preview`. You use `preview` when you want those articles that are either in published or draft state. Since you need only one article, the `slug` parameter is used to search for an article by its slug. The response from Strapi API is an array, so you fetch the first item in the `response`. Each article has a unique slug, so the `response` array will contain a single item.

Since you have referred to the `STRAPI_URL` as an environment variable, you need to add it to a `.env` file. To do so, create a `.env` file at the root of your Next.js project and add the following secret to it:

```js
    STRAPI_URL=http://localhost:1337/api
```
Save your progress and start your Next.js development server by running:
```bash
    npm run dev
```
Visit [localhost:3000/articles](http://localhost:3000/articles) and you’ll see your articles page rendered by Next.js:

![List of articles](https://paper-attachments.dropbox.com/s_78E48E438BAD42C0DC175A0956096BD58B7F69A6652A796D6969EC2C75E7774D_1658911009684_list-of-articles-in-next-app.png)

Although you have six articles in Strapi CMS, you’ll only see five since one article is in draft state.

## Designing a Single Article Page

The next step is to design a single article page that needs to be [dynamic](https://nextjs.org/learn/basics/dynamic-routes). You can fetch your article using Strapi API based on the slug parameter provided in the URL path.

In the `pages/articles` directory, create a `[slug].js` file and add the following code:

```js
    import MarkdownView from 'react-showdown';
    import { Container, Row, Col } from 'react-bootstrap';
    import { fetchArticlesApi, fetchArticleApi } from '/lib/articles';
    
    const ArticleView = (props) => {
      const { article } = props;
      return (
        <section className="py-5">
          <Container>
            <Row>
              <Col lg="7" className="mx-lg-auto">
                <h1>{article.data[0].attributes.title}</h1>
                <MarkdownView markdown={article.data[0].attributes.content} />
              </Col>
            </Row>
          </Container>
        </section>
      );
    };
    
    // 1
    export async function getStaticPaths() {
      const articles = await fetchArticlesApi();
      const paths = articles.data.map((article) => ({ params: { slug: article.attributes.slug } }));
      return {
        paths: paths,
        fallback: false,
      };
    }
    
    export async function getStaticProps(context) {
      // 2
      const slug = context.params.slug;
      if (!slug) {
        throw new Error('Slug not valid');
      }
      
      // 3
      const article = await fetchArticleApi(slug);
      if (!article) {
        return { notFound: true };
      }
    
      // 4
      return { props: { article } };
    }
    
    export default ArticleView;
```

In the above code:
1. You need to return a list of all possible values for the slug as Next.js needs to build a dynamic route. In the `getStaticPaths` function, you make a call to the `fetchArticlesApi` function, fetch all the published articles, and map their slugs to the `path` array.
2. In the `getStaticProps` function, you get the `slug` from the URL params. If the `slug` is not valid, you throw an error saying “Slug not valid.”
3. You fetch an `article` based on the `slug` parameter by calling the `fetchArticleApi`. You redirect the user to the not found page in case the article is not valid.
4. The `article` is passed as a prop, which is then used in the `ArticleView`.

Save your progress. Click **Read More** on any article to open the single article page:

![Single article](https://i.imgur.com/ssxtX7I.png)

## Enabling the Preview Mode
Now that your blog is designed, you need to configure the [Next.js Preview API](https://nextjs.org/docs/advanced-features/preview-mode) for viewing the drafts on your Next.js website.

In your Next.js project, add the following secret to the `.env` file:

```
    CLIENT_PREVIEW_SECRET=<long random string>
```

The `CLIENT_PREVIEW_SECRET` variable is used to securely access the preview mode in the browser. It is passed as a query parameter to the preview endpoint when you want to enable the preview mode.

In the `pages/api` directory, create a `preview.js` file and add the following code:

```js
    import { fetchArticlePreviewApi } from '/lib/articles';
    
    export default async function handler(req, res) {
      // Get the preview secret and the slug which needs to be previewed
      const secret = req.query.secret;
      const slug = req.query.slug;
      // If the secret passed as URL query parameter doesn't match the preview secret in .env
      // then send a 401-Unauthorized response
      if (secret !== process.env.CLIENT_PREVIEW_SECRET) {
        return res.status(401).json({ message: 'Invalid token' });
      }
    
      // If slug is not provided, send a 400-Bad Request response
      if (!slug) {
        return res.status(400).json({ message: 'Parameter `slug` is not provided' });
      }
    
      // Send a request to Strapi and fetch the article
      // to check if the provided slug exists
      const article = await fetchArticlePreviewApi(slug);
    
      // If the article is not found, send a 404-Not Found response
      if (article === null) {
        return res.status(404).json({ message: 'Article not found' });
      }
    
      // Enable Preview Mode by setting the cookies
      res.setPreviewData({});
    
      // Redirect to the path of the article slug
      res.redirect(307, `/articles/${article.data[0].attributes.slug}`);
    }
```
In the above code:
1. You destructure the `secret` and `slug` from the `req.query` object.
2. You check whether the query parameter `secret` matches the preview secret (`CLIENT_PREVIEW_SECRET`).
3. You make sure that the `slug` is not `null`. In case it is, you send back a bad request response (`res.status(400)`).
4. You fetch the `article` associated with the requested slug by calling the `fetchArticlePreviewApi` function. You pass the `slug` parameter and fetch the articles in `preview` mode.
5. You make sure that the fetched `article` is not `null`. In case it is, you send back a not found response (`res.status(404)`).
6. After verifying the request, you call the `res.setPreviewData({})` function, which is provided by the Next.js Preview API. It sets cookies in your browser which are valid for the current session.
7. You redirect (`res.redirect()`) the user to the requested article page (`/articles/${article.data[0].attributes.slug}`) using a [307 redirect](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307#:~:text=HTTP%20307%20Temporary%20Redirect%20redirect,to%20perform%20the%20redirected%20request).

The cookies set by preview mode API remain in the browser as long as you don’t close the browser window. You might need an API endpoint to turn off the preview mode manually and clear all cookies.

In the `pages/api` directory, create an `exit-preview.js` file and add the following code:

```js
    export default function handler(req, res) {
      // Clear the cookies set by the preview mode.
      res.clearPreviewData();
    
      // Send a 200-Success response to the frontend
      res.status(200).end();
    }
```
In the above code:
1. You call the `res.clearPreviewData()` function to delete the cookies set by the preview mode API.
2. You send back a success response using `res.status(200).end()`.

Now you need to update the single article page to implement the preview mode functionality. Open the `pages/articles/[slug].js` file and update it to match the following code:

```js
    import Router from 'next/router';
    import MarkdownView from 'react-showdown';
    import { Container, Row, Col } from 'react-bootstrap';
    import { fetchArticlesApi, fetchArticleApi, fetchArticlePreviewApi } from '/lib/articles';
    
    const ArticleView = (props) => {
      // 4
      const { article, previewMode } = props
      return (
        <section className="py-5">
          <Container>
            <Row>
              <Col lg="7" className="mx-lg-auto">
                {/* 5 */}
                {previewMode ? (
                  <div className="small text-muted border-bottom mb-3">
                    <span>You are currently viewing in Preview Mode. </span>
                    <a role="button" className="text-primary" onClick={() => exitPreviewMode()}>
                      Turn Off Preview Mode
                    </a>
                  </div>
                ) : (
                  ''
                )}
                <h1>{article.data[0].attributes.title}</h1>
                <MarkdownView markdown={article.data[0].attributes.content} />
              </Col>
            </Row>
          </Container>
        </section>
      );
    };
    // 6
    
    async function exitPreviewMode() {
      const response = await fetch('/api/exit-preview');
      if (response) {
        Router.reload(window.location.pathname);
      }
    }
    
    export async function getStaticPaths() {
      const articles = await fetchArticlesApi();
      const paths = articles.data.map((article) => ({ params: { slug: article.attributes.slug } }));
      return {
        paths: paths,
        fallback: false,
      };
    }
    
    export async function getStaticProps(context) {
      const slug = context.params.slug;
      if (!slug) {
        throw new Error('Article not found');
      }
            
      // 1
      const previewMode = context.preview ? true : null;
      
      // 2
      let article;
      if (previewMode) {
        article = await fetchArticlePreviewApi(slug);
      } else {
        article = await fetchArticleApi(slug);
      }
    
      if (!article) {
        return { notFound: true };
      }
            
      return {
        props: {
          article,
          previewMode, // 3
        }
      };
    }
    
    export default ArticleView;
```

In the above code:
1. When you view a page in preview mode, the `preview` property is set to `true` and added to the `context` object, which is passed as a parameter to the `getStaticProps` function. Based on the value of `context.preview`, you decide the value of `previewMode`.
2. If `previewMode` is `true`, you fetch the `article` associated with the requested slug in `preview` mode by calling the `fetchArticlePreviewApi` function; otherwise, you fetch the article using the `fetchArticleApi` function.
3. You return the `previewMode` as a prop along with the `article`.
4. `previewMode` is then destructured in the `ArticleView`.
5. In the JSX template, if `previewMode` is set to `true`, you display a text stating that the preview mode is enabled and a button to close the preview mode. Clicking this button calls the `exitPreviewMode` function.
6. The `exitPreviewMode` function is used to turn off the preview mode by calling the exit preview API endpoint, ie `/api/exit-preview`, then reloads the current article page after receiving a response.

## Accessing Preview Mode
Save your progress. Since you have added environment variables to your Next.js project, you need to restart your Next.js development server. Once the server restarts, visit [http://localhost:3000/api/preview?secret=<secret>&slug=<slug>](http://localhost:3000/api/preview?secret=%3Csecret%3E%3C=%3Cslug%3E) by replacing:


- `<secret>` with the preview secret defined in the `.env` file
- `<slug>` with the slug of a draft article

If you have passed the correct `secret` and `slug` query parameters, you will be redirected to [http://localhost:3000/articles/<slug>](http://localhost:3000/articles/%3Cslug%3E):

![A draft article in preview mode in Next.js](https://i.imgur.com/wS121UU.png)

If you want to know more about the cookies set by the preview mode, open your browser’s Developer Tools and open the Cookies section. You’ll notice the `__prerender_bypass` and `__next_preview_data` cookies set in your browser:

![List of cookies set by the Next.js preview mode API](https://i.imgur.com/gFoChT0.png)

At this point, you can test the `exit-preview` API endpoint as well. Click **Turn Off Preview Mode** at the top of the page to exit the preview mode. It will delete all the cookies and refresh the page. You’ll be redirected to the 404 - Not Found page:

![404 not found page in Next.js](https://i.imgur.com/KatzODl.png)

## Adding Preview Button in Strapi
Visiting the preview URL directly is not handy, especially when it involves complex slugs and long preview secrets. The best thing to do is to add a preview button in Strapi Content Manager.

To do so, you need to create a preview button component in React and inject it into Strapi Content Manager. This is done by [creating a Strapi plugin](https://docs.strapi.io/developer-docs/latest/development/plugins-development.html#create-a-plugin).

At the root (`backend`) of the Strapi project, generate a plugin using the following command:

```bash
    npm run strapi generate
```

1. Choose "plugin" from the list, press Enter, and give the plugin a name in kebab-case. Name it `preview-button`.
2. Choose `JavaScript` for the plugin language.

Make a `PreviewLink` directory for your `preview-button` plugin.
```bash
    mkdir -p ./src/plugins/preview-button/admin/src/components/PreviewLink
```
Create an `index.js` file in `./src/plugins/preview-button/admin/src/components/PreviewLink/` to provide a link for the Preview button.

```js
    // ./src/plugins/preview-button/admin/src/components/PreviewLink/index.js
    import React from 'react';
    import { useCMEditViewDataManager } from '@strapi/helper-plugin';
    import Eye from '@strapi/icons/Eye';
    import { LinkButton } from '@strapi/design-system/LinkButton';
    
    const PreviewLink = () => {
      const {initialData} = useCMEditViewDataManager();
      if (!initialData.slug) {
        return null;
      }
    
      return (
        <LinkButton
          size="S"
          startIcon={<Eye/>}
          style=
          href={`${CLIENT_FRONTEND_URL}?secret=${CLIENT_PREVIEW_SECRET}&slug=${initialData.slug}`}
          variant="secondary"
          target="_blank"
          rel="noopener noreferrer"
          title="page preview"
        >Preview
        </LinkButton>
      );
    };
    
    export default PreviewLink;
```

The above code creates a `PreviewLink` React component. In the `StyledPreviewLink` component, you have specified a `href` attribute that navigates the user to the preview API endpoint on the Next.js website. It makes reference to `CLIENT_FRONTEND_URL` and `CLIENT_PREVIEW_SECRET`, so you need to add these variables as environment variables.

To do so, open the `.env` file at the root of the Strapi project and add the following environment variables:

```
    CLIENT_PREVIEW_SECRET=<preview-secret>
    CLIENT_FRONTEND_URL=http://localhost:3000
```
Add the same preview secret that you added in your Next.js project.

Since these variables need to be accessed on Strapi’s admin frontend, you need to add a custom webpack configuration.

Rename *./src/admin/webpack.config.example.js* to *./src/admin/webpack.config.js*. Refer to the [v4 code migration: Updating the webpack configuration](https://docs.strapi.io/developer-docs/latest/update-migration-guides/migration-guides/v4/code/frontend/webpack.html) from the Official Strapi v4 Documentation for more information.

Edit *./src/admin/webpack.config.js.* Refer to the Official webpack docs for more information: [DefinePlugin | webpack](https://webpack.js.org/plugins/define-plugin/).

```js
    // ./src/admin/webpack.config.js
    'use strict';
    /* eslint-disable no-unused-vars */
    module.exports = (config, webpack) => {
      // Note: we provide webpack above so you should not `require` it
      // Perform customizations to webpack config
      // Important: return the modified config
      config.plugins.push(
        new webpack.DefinePlugin({
          CLIENT_FRONTEND_URL: JSON.stringify(process.env.CLIENT_FRONTEND_URL),
          CLIENT_PREVIEW_SECRET: JSON.stringify(process.env.CLIENT_PREVIEW_SECRET),
        })
      )
      return config;
    };
```

The above code injects the `CLIENT_FRONTEND_URL` and `CLIENT_PREVIEW_SECRET` as global variables so that they can be used anywhere in the Strapi admin frontend.

Finally, to inject the `PreviewLink` component in the Strapi Content Manager, edit the `index.js` file at `./src/plugins/preview-button/admin/src/index.js` with the following code:

```js
    // ./src/plugins/preview-button/admin/src/index.js
    import { prefixPluginTranslations } from '@strapi/helper-plugin';
    import pluginPkg from '../../package.json';
    import pluginId from './pluginId';
    import Initializer from './components/Initializer';
    import PreviewLink from './components/PreviewLink';
    import PluginIcon from './components/PluginIcon';
    
    const name = pluginPkg.strapi.name;
    export default {
      register(app) {
        app.addMenuLink({
          to: `/plugins/${pluginId}`,
          icon: PluginIcon,
          intlLabel: {
            id: `${pluginId}.plugin.name`,
            defaultMessage: name,
          },
          Component: async () => {
            const component = await import(/* webpackChunkName: "[request]" */ './pages/App');
            return component;
          },
    
          permissions: [
            // Uncomment to set the permissions of the plugin here
            // {
            //   action: '', // the action name should be plugin::plugin-name.actionType
            //   subject: null,
            // },
          ],
        });
    
        app.registerPlugin({
          id: pluginId,
          initializer: Initializer,
          isReady: false,
          name,
        });
      },
    
      bootstrap(app) {
        app.injectContentManagerComponent('editView', 'right-links', {
          name: 'preview-link',
          Component: PreviewLink
        });
      },
    
      async registerTrads({ locales }) {
        const importedTrads = await Promise.all(
          locales.map(locale => {
            return import(
              /* webpackChunkName: "translation-[request]" */ `./translations/${locale}.json`
            )
    
              .then(({ default: data }) => {
                return {
                  data: prefixPluginTranslations(data, pluginId),
                  locale,
                };
              })
    
              .catch(() => {
                return {
                  data: {},
                  locale,
                };
              });
          })
        );
        return Promise.resolve(importedTrads);
      },
    };
```
The above code injects the `PreviewLink` component into the content manager.

Next enable the plugin by creating `./config/plugins.js`.

```js
    // ./config/plugins.js
    module.exports = {
        // ...
        'Preview Button': {
          enabled: true,
          resolve: './src/plugins/preview-button' // path to plugin folder
        },
        // ...
      }
```
All the above additions make changes to the Strapi admin frontend in React. You need to rebuild the admin panel.

To do so, shut down Strapi’s development server and rebuild the admin panel by running the following command:

```bash
    npm run build
```
Once the rebuild is complete, start Strapi’s development server by running the following command:

```bash
    npm run develop
```
Wait for the server to start and then visit [localhost:1337/admin](http://localhost:1337/admin). Open any draft article. You’ll see a **Preview** button at the right-hand side of the content manager:

![Preview button in Strapi Content Manager](https://paper-attachments.dropbox.com/s_78E48E438BAD42C0DC175A0956096BD58B7F69A6652A796D6969EC2C75E7774D_1659036650713_preview-button-in-strapi-content-manager.png)

In case you’re not able to see the **Preview** button, delete the `.cache` and `build` directories by running `rm -rf .cache build`, then rebuild your Strapi admin frontend by running `npm run build`.

At this point, you can test whether the preview button works. Update the content of your draft article and click **Save**.


![Remove highlighted text and click **Save**](https://paper-attachments.dropbox.com/s_78E48E438BAD42C0DC175A0956096BD58B7F69A6652A796D6969EC2C75E7774D_1659036740739_remove-highlighted-text-and-click-save.png)

Click the **Preview** button. You’ll be directed to the single article page on your Next.js website.

![Updated draft article](https://i.imgur.com/M5u0yzb.png)

## Conclusion

Great work! One of the best things of Strapi is that it is open-source. Even if it doesn’t have a certain feature, you can customize Strapi according to your needs. This is exactly what you did today. Your content editors will love the preview mode because they can use it to view their content without building the entire website.

For a similar option, you can [implement Previews in a](https://strapi.io/blog/implementing-previews-with-nuxt-applications-using-a-strapi-backend) [**Nuxt.js**](https://strapi.io/blog/implementing-previews-with-nuxt-applications-using-a-strapi-backend) [app](https://strapi.io/blog/implementing-previews-with-nuxt-applications-using-a-strapi-backend) using Strapi as a backend.
The entire source code for this tutorial is available in this [GitHub repository](https://github.com/Marktawa/strapi-nextjs-previews).