Unpublished previews not working

Hi, im trying to setup previews on our site using the v4 plugin, with the help of the official docs I managed to get normal previews working fine, but I can't seem to get unpublished previews working.

preview.tsx
import * as React from 'react';

import { withPrismicPreviewResolver } from 'gatsby-plugin-prismic-previews';

// Update the path to your Link Resolver

import { linkResolver } from '../utils/linkResolver';

const PreviewPage = () => {
  return (
    <div>
      <h1>Loading preview…</h1>
    </div>
  );
};

export default withPrismicPreviewResolver(PreviewPage, [
  {
    repositoryName: process.env.PRISMIC_REPOSITORY_NAME || 'boxinc',
    linkResolver,
  },
]);
404.tsx
import React, { FC } from 'react';

import { graphql, PageProps } from 'gatsby';
import {
  componentResolverFromMap,
  withPrismicUnpublishedPreview,
} from 'gatsby-plugin-prismic-previews';
import { useTranslation, Trans } from 'react-i18next';
import styled from 'styled-components';

import { mediaMaxMobile } from '../components/global/breakpoints';
import { StyledA } from '../components/global/button';
import { LocalizedLink } from '../components/global/link';
import SEO from '../components/global/seo';
import {
  Heading1Css,
  Heading2Css,
  Heading3Css,
  Text1Css,
  Text2Css,
} from '../components/global/typography';
import BlogPostTemplate from '../templates/blogPost';
import InfoPageTtemplate from '../templates/infoPage';
import PageTemplate from '../templates/page';
import PageCoreTemplate from '../templates/pageCore';
import { Theme } from '../types/global';
import { linkResolver } from '../utils/linkResolver';
import { wrapPage } from '../utils/wrapPage';

const CenteredContent = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  margin: 52px 0 6rem 0;
`;

const TitleWrapper = styled.div`
  padding: 6.25rem 5rem;
  text-align: center;
  width: 100%;
  background-color: ${({ theme }) => theme.colors.background};
`;

const InfoTitle = styled.span`
  ${Heading1Css};
  display: block;
  max-width: 800px;
  margin: 0 auto;

  color: ${({ theme }) => theme.colors.lightText};

  ${mediaMaxMobile`
    ${Text2Css};
    color: ${({ theme }: Theme) => theme.colors.lightText};
  `};
`;

const ContentWrapper = styled.div`
  max-width: 690px;
  padding: 5rem 1rem;
  margin: 0 auto;
  text-align: center;
`;

const Message = styled.p`
  ${Heading2Css};
  color: ${({ theme }) => theme.colors.lightText};
  margin-bottom: 0;

  ${mediaMaxMobile`
    ${Heading3Css};
    color: ${({ theme }: Theme) => theme.colors.lightText};
  `};
`;
const Note = styled.div`
  ${Text1Css};
  max-width: 500px;
  color: ${({ theme }) => theme.colors.lightText};
  padding: 2.5rem 0;
  margin: 0 auto;

  ${mediaMaxMobile`
    ${Text2Css};
  `};
`;

const NotFoundPage: FC<PageProps> = () => {
  const { t } = useTranslation();

  return (
    <CenteredContent>
      <SEO title={t('errors.page_not_found_title')} />
      <TitleWrapper>
        <InfoTitle>{t('errors.page_not_found_title')}</InfoTitle>
      </TitleWrapper>

      <ContentWrapper>
        <Message>{t('errors.page_not_found_text')}</Message>
        <Note>
          <Trans i18nKey="errors.general_note">
            Pretext
            <LocalizedLink to={'/'}>Marketing</LocalizedLink>
            or
            <StyledA href={`mailto:${t('customer_service_email')}`}>
              Contact
            </StyledA>
            postText
          </Trans>
        </Note>
      </ContentWrapper>
    </CenteredContent>
  );
};

export const query = graphql`
  query NotFoundPageQuery($lang: String!) {
    allPrismicNavigation(filter: { lang: { eq: $lang } }) {
      ...LayoutNavigationFragment
    }
    allPrismicFooter(filter: { lang: { eq: $lang } }) {
      ...LayoutFooterFragment
    }
  }
`;

// @ts-ignore
export default withPrismicUnpublishedPreview(wrapPage(NotFoundPage), [
  {
    repositoryName: 'boxinc',
    linkResolver,
    componentResolver: componentResolverFromMap({
      page: PageTemplate,
    }),
  },
]);
Page.tsx
import React, { FC } from 'react';

import { graphql, PageProps } from 'gatsby';
import { withPrismicPreview } from 'gatsby-plugin-prismic-previews';
import { shape } from 'prop-types';

import { LayoutFooterFragment } from '../components/global/footer';
import {
  LayoutNavigationFragment,
  LinkFragment,
} from '../components/global/navigation';
import Page from '../components/page';
import { linkResolver } from '../utils/linkResolver';
import { wrapPage } from '../utils/wrapPage';

type PageTemplateProps = PageProps & {
  data: { [key: string]: any };
};

const PageTemplate: FC<PageTemplateProps> = ({ data }) => {
  const doc = data.allPrismicPage.nodes[0];

  if (doc) {
    return <Page {...Page.mapFragmentToProps(doc)} />;
  }

  return null;
};

PageTemplate.propTypes = {
  data: shape({}).isRequired,
};

const WrappedPage = wrapPage(PageTemplate);

WrappedPage.fragments = [
  LayoutFooterFragment,
  LayoutNavigationFragment,
  LinkFragment,
  // @ts-ignore
  ...Page.fragments,
];

// @ts-ignore
export default withPrismicPreview(WrappedPage, [
  {
    repositoryName: process.env.PRISMIC_REPOSITORY_NAME || 'boxinc',
    linkResolver,
  },
]);

export const query = graphql`
  query PageQuery($uid: String!, $lang: String!) {
    allPrismicPage(filter: { uid: { eq: $uid }, lang: { eq: $lang } }) {
      ...PageFragment
    }
    allPrismicNavigation(filter: { lang: { eq: $lang } }) {
      ...LayoutNavigationFragment
    }
    allPrismicFooter(filter: { lang: { eq: $lang } }) {
      ...LayoutFooterFragment
    }
  }
`;

When I try to preview unpublished doc through prismic, I get to the 404 page as expected, the preview is loading and after the loading nothing happens, I stay on the 404 page.

I tried console logging the nodes argument given to the componentResolver function, that returned an empty array, which doesnt sound right to me.
Also when I looked into React devTools, PrismicPreviewProvider had isBootstrap = true and it contained all the page data including the unpublished preview I wanted to view.

What am I missing here? Any help is appreciated.

Hello team, thanks for reaching out

You should be able to preview any unpublished documents if your Link Resolver and withPrismicUnpublishedPreview HOC are correctly set up.

We're currently running some tests to see if there's something wrong on our side. I'll let you know when we have more information.

Thanks

Thanks for looking into it.

This is what our link resolver looks like.

linkResolver.js
// @ts-nocheck
const { normalizeLanguage } = require('./normalizeLanguage');

module.exports = {
  linkResolver(doc) {
    const isBlogPage = (() => {
      if (doc.type === 'page' && doc.data && doc.data.is_blog) {
        return true;
      }
      return false;
    })();

    if (
      (doc.type === 'page' && !isBlogPage) ||
      doc.type === 'info-page' ||
      doc.type === 'page-core'
    ) {
      const lang = normalizeLanguage(doc.lang);
      const path = doc.uid.replace(/\./g, '/');
      return `/${lang}/${path}`;
    }

    if (doc.type === 'blog-post' || isBlogPage) {
      const lang = normalizeLanguage(doc.lang);
      const path = doc.uid.replace(/\./g, '/');
      let categorySlug;

      if (doc.data && doc.data.categories[0].category.uid) {
        categorySlug = doc.data.categories[0].category.uid;
      }

      if (categorySlug) {
        return `/${lang}/blog/${categorySlug}/${path}`;
      }

      // This shouldn't happen if the categories are set up correctly in Prismic
      return `/${lang}/blog/${path}`;
    }

    return '/';
  },
};

Hey there.

Your Link Resolver seems to be just fine. I forgot to mention earlier that Gatsby's 404 pages in development mode will appear even when a preview session is launched. This means that, when you see the 404 page, you'll need to manually go to the correct URL of the unpublished document. It should appear in a list of the same err page.

This would work normally in production. So it's only in dev mode when this odd behaviour will keep happening.

Hey.
I tried it in production, but the behavior stays the same. I launch a preview session, I get redirected to the custom 404 page with the unpublished preview HOC, preview is loading, after loading finishes, I stay on the 404 page.

Hey there, maybe there's something missing in your plugin configuration. Can you show me how you're passing the Link resolver to the plugin options? sometimes it just takes a small change in the syntax to make it work with version4 of the plugin.

linkResolver: require('./src/utils/linkResolver').linkResolver,

I ran into the same issue and was pulling my hair out trying to figure it out.

I solved it by ensuring my 404 page pulls in the data value from the props.

import * as React from "react"
import {withPrismicUnpublishedPreview} from "gatsby-plugin-prismic-previews"

import { unpublishedRepositoryConfigs } from '../utils/prismicUnpublishedPreviews'

const NotFoundPage = ({data}) => {

  return (
    <div>
      <h1>Not found</h1>
    </div>
  )
}

and in development when you preview an unpublished page you get taken to the development 404 page.
Just click on preview custom 404 page button near the top

Hope this helps

Hi, thanks for your suggestion, unfortunately it doesn't seem to work for me.

Hi, this is what our plugin config looks like

gatsby-config
/* eslint-disable no-underscore-dangle */
/*
  Gatsby can access environment variables in .env.production and .env.development files only for Client side js code.
  To access environment variables within Server side node.js code we need to load them using dotenv.
  We load also variables from .env file that are ignored by Gatsby
*/
require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` });
require('dotenv').config({ path: `.env` });

const { BOXINC_SITE_URL } = require('./src/config');
const { linkResolver } = require('./src/utils/linkResolver');
const { normalizeLanguage } = require('./src/utils/normalizeLanguage');

module.exports = {
  siteMetadata: {
    siteUrl: BOXINC_SITE_URL,
  },
  plugins: [
    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-prismic`,
      options: {
        repositoryName: `${process.env.PRISMIC_REPOSITORY_NAME}`,
        accessToken: `${process.env.PRISMIC_API_KEY}`,
        lang: '*',
        linkResolver: (doc) => linkResolver(doc),
        schemas: {
          /* eslint-disable global-require */
          'blog-author': require('./prismic/schema/blog-author.json'),
          'blog-category': require('./prismic/schema/blog-category.json'),
          'blog-post': require('./prismic/schema/blog-post.json'),
          'blog-posts-page': require('./prismic/schema/blog-posts-page.json'),
          footer: require('./prismic/schema/footer.json'),
          'info-page': require('./prismic/schema/info-page.json'),
          menu: require('./prismic/schema/menu.json'),
          navigation: require('./prismic/schema/navigation.json'),
          'page-core': require('./prismic/schema/page-core.json'),
          page: require('./prismic/schema/page.json'),
          'section-content': require('./prismic/schema/section-content.json'),
          advantages: {},
          'blog-post-type-1': {},
          header: {},
          nginx_redirects: require('./prismic/schema/nginx-redirects.json'),
          'redirect-rule': require('./prismic/schema/redirect-rule.json'),
          /* eslint-enable global-require */
        },
      },
    },
    {
      resolve: `gatsby-plugin-prismic-previews`,
      options: {
        repositoryName: `${process.env.PRISMIC_REPOSITORY_NAME}`,
        accessToken: `${process.env.PRISMIC_API_KEY}`,
      },
    },
    { resolve: `gatsby-plugin-react-helmet-async` },
    { resolve: `gatsby-transformer-json` },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/assets/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/locale`,
        name: `locale`,
      },
    },
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        icon: 'src/assets/images/favicon-32x32.png',
      },
    },
    {
      resolve: `gatsby-plugin-styled-components`,
      options: {},
    },

    {
      resolve: 'gatsby-plugin-react-svg',
      options: {
        rule: {
          include: `${__dirname}/src/assets/images/icons`,
        },
      },
    },
    {
      resolve: `gatsby-plugin-sitemap`,
      options: {
        query: `
          {
            allPrismicPage {
              nodes {
                path: url
                lang
                alternateLanguages: alternate_languages {
                  uid
                  lang
                }
                data {
                  categories {
                    category {
                      uid
                    }
                  }
                  metaCanonical: meta_canonical
                  metaDescription: meta_description
                  metaKeywords: meta_keywords
                  metaRobots: meta_robots
                  metaTitle: meta_title
                }
                firstPublicationDate: first_publication_date
              }
            }
            allPrismicPageCore {
              nodes {
                path: url
                lang
                alternateLanguages: alternate_languages {
                  uid
                  lang
                }
                data {
                  metaCanonical: meta_canonical
                  metaDescription: meta_description
                  metaKeywords: meta_keywords
                  metaRobots: meta_robots
                  metaTitle: meta_title
                }
              }
            }
            allPrismicBlogPost {
              nodes {
                __typename
                lang
                path: url
                data {
                  categories {
                    category {
                      uid
                    }
                  }
                  heroImage: hero_image {
                    url
                  }
                  metaKeywords: meta_keywords
                  metaTitle: meta_title
                  metaCanonical: meta_canonical
                  metaDescription: meta_description
                  metaRobots: meta_robots
                  publicationDate: release_date
                }
                alternateLanguages: alternate_languages {
                  uid
                  lang
                }
              }
             }
            }
        `,
        output: '/',
        excludes: ['de/dummy', 'en/dummy'],
        resolveSiteUrl: () => BOXINC_SITE_URL,
        resolvePages: ({
          allPrismicPage: { nodes: allPage },
          allPrismicPageCore: { nodes: allPageCore },
          allPrismicBlogPost: { nodes: allPrismicBlogPost },
        }) => [...allPage, ...allPageCore, ...allPrismicBlogPost],
        filterPages: (page, excludedRoute, _) => {
          if (page.path === excludedRoute) {
            return true;
          }

          if (
            page.data.metaRobots &&
            page.data.metaRobots.includes('noindex')
          ) {
            return true;
          }

          return false;
        },
        serialize: ({
          path,
          lang,
          alternateLanguages,
          __typename,
          data,
          firstPublicationDate,
        }) => {
          if (
            __typename === 'PrismicBlogPost' ||
            (data.categories &&
              data.categories.length > 0 &&
              data.categories[0].category.uid)
          ) {
            return {
              url: path,
              links: alternateLanguages.map(({ uid, lang: altLang }) => ({
                lang: altLang,
                url: `/${normalizeLanguage(altLang)}/blog/${
                  data.categories[0].category.uid
                }/${uid}`,
              })),
              news: data.metaTitle && {
                publication: {
                  name: 'Box Inc',
                  language: lang,
                },
                genres: 'Blog',
                publication_date: data.publicationDate || firstPublicationDate,
                title: data.metaTitle,
                keywords: data.metaKeywords,
              },
            };
          }

          return {
            url: path,
            lang,
            links: alternateLanguages.map(({ uid, lang: altLang }) => ({
              lang: altLang,
              url: `/${normalizeLanguage(altLang)}/${uid}`,
            })),
          };
        },
      },
    },
    {
      resolve: 'gatsby-plugin-brotli',
      options: {
        extensions: ['css', 'html', 'js', 'json', 'svg'],
      },
    },
    {
      resolve: 'gatsby-plugin-webpack-bundle-analyser-v2',
      options: {
        analyzerMode: 'static', // If we ever need to analyze our bundle size, just replace 'disabled' with 'static' or 'server'
        openAnalyzer: true,
      },
    },
    {
      resolve: 'nginx-redirect-rules-gen',
      options: {
        outputConfigFile: `${__dirname}/nginx/default.conf`,
        inputConfigFile: `${__dirname}/nginx/default.source.conf`,
      },
    },
  ],
};

Hi @customerservice,

I'm Angelo, the developer of the Gatsby plugins.

Everything you provided looks technically correct. Since you are seeing isBootstrapped: true in <PrismicPreviewProvider>, that means the plugin is correctly connecting to your Prismic repository and pulling all previewed documents.

The only difference I see between your Page.tsx and 404.tsx pages is the way the Prismic repository is passed. Can you confirm that you are passing the correct repository name on 404.tsx (do you use a different Prismic Repository name during production?)?

{
  repositoryName: process.env.PRISMIC_REPOSITORY_NAME || 'boxinc',
}

I tried console logging the nodes argument given to the componentResolver function, that returned an empty array, which doesnt sound right to me.

This is the reason your 404 page renders your normal 404 page and not the previewed document.

The list of matching nodes is used to determine which template to render. If you are using the componentResolverFromMap helper function, it automatically uses the first node in the list and matches its type to your template map. If you need more granular control, you can write your own function that returns a React component to render.

Since your node list is empty, withPrismicUnpublishedPreview does not have a template component to render.

The list of matching nodes is determined using the following:

  • When all documents are fetched from your repository during a preview session, it resolves its URL using your Link Resolver and assigns it to its url field. The Link Resolver provided to withPrismicPreview and withPrismicUnpublishedPreview is used.
  • When your 404 page is rendered with withPrismicUnpublishedPreview, it uses the current URL to find nodes with matching URLs.

Image you have the following documents in your repository which return the following URLs:

  • Page 1 => /page-1
  • Page 2 => /page-2
  • Blog Post 1 => /blog/post-1
  • Blog Post 2 => /blog/post-2

Now imagine you write a blog post and preview it before publishing it (i.e. an unpublished preview)

  • Blog Post 3 => /blog/post-3 (unpublished)

When you click the preview button, you should land on your /preview page. This will redirect you to /blog/post-3, which is a 404 page since it does not exist on your site.

The 404 page will look at the URL and find a document that matches. It finds Blog Post 3, sees that it is a blog_post type, and renders the BlogPost template provided to withPrismicUnpublishedPreview.

The result of the Link Resolver for the unpublished document must match the URL you land on.


If this explanation does not help solve the issue, I can take a look at your project's code and run it locally. You can send your code to me in a private message if you are comfortable with that.

This thread has been closed due to inactivity. Flag to reopen.

Hi, I'm trying to setup previews on our site using the v4 plugin, with the help of the official docs I managed to get normal previews working fine, but I can't seem to get unpublished previews working.
I've read this entire thread but still can't solve the problem.

gatsby-browser.js (728 Bytes)
gatsby-ssr.js (400 Bytes)
Layout.js (918 Bytes)
ErrorPage.js (36.9 KB)
HomePage.js (36.8 KB)
prismicPreviews.js (1.2 KB)
Utilities.js (4.5 KB)

Here's my relevant gatsby-node.js part

// Query all Pages with their IDs and template data.
   const pages = await graphql(`
    query {
      allPrismicHomePage {
          nodes {
            id
            uid
            lang
            type
            tags
            url
            type
            data {
               meta_title
               meta_description
            }
            alternate_languages {
             id
             type
             lang
             uid
           }
          }  
     }
    }                                     
  `)

   // Create pages for each Page in Prismic using the selected template.
   pages.data.allPrismicHomePage.nodes.forEach((page) => {
      let template = path.resolve(__dirname, 'src/templates/HomePage.js');
      if(page.tags.includes('error')) {
         template = path.resolve(__dirname, 'src/templates/ErrorPage.js');
      }
      createPage({
         path: linkResolver(page),
         component: template,
         context: {
            ...page,
            uid: page.uid,
            lang: page.lang
         },
      })
   })

And this is gatsby-config relative to prismic plugins

/*
    * Gatsby's data processing layer begins with “source”
    * plugins. Here the site sources its data from prismic.io.
    */
    {
      resolve: 'gatsby-source-prismic',
      options: {
        // The name of your prismic.io repository. This is required.
        // Example: 'gatsby-source-prismic-test-site' if your prismic.io address
        // is 'gatsby-source-prismic-test-site.prismic.io'.
        repositoryName: process.env.GATSBY_PRISMIC_REPO_NAME,

        // An API access token to your prismic.io repository. This is optional.
        // You can generate an access token in the "API & Security" section of
        // your repository settings. Setting a "Callback URL" is not necessary.
        // The token will be listed under "Permanent access tokens".
        accessToken: process.env.PRISMIC_ACCESS_TOKEN,

        // If you provide a release ID, the plugin will fetch data from Prismic
        // for a specific release. A Prismic release is a way to gather a
        // collection of changes for a future version of your website. Note that
        // if you add changes to a release, you'll need to rebuild your website
        // to see them.
        // See: https://user-guides.prismic.io/en/collections/22653-releases-scheduling#the-basics-of-a-release
        //releaseID: 'example-eiyaingiefahyi7z',


        // Set an HTML serializer function used to process formatted content.
        // Fields with rich text formatting use this function to generate the
        // correct HTML.
        // The document node, field key (i.e. API ID), and field value are
        // provided to the function, as seen below. This allows you to use
        // different HTML serializer logic for each field if necessary.
        // See: https://prismic.io/docs/nodejs/beyond-the-api/html-serializer
        htmlSerializer: (type, element, content, children) => {
          // Your HTML Serializer
        },

        // Provide an object of Prismic custom type JSON schemas to load into
        // Gatsby. This is required.
        schemas: {
          // Your custom types mapped to schemas
          link_cards_grid: {},
          home_page: require('./src/schemas/home_page.json'),
          navigation: require('./src/schemas/navigation.json'),
          news: require('./src/schemas/news.json'),
          real_estate_object: require('./src/schemas/real_estate_object.json'),
          design_styles: require('./src/schemas/design_styles.json'),
          academy: require('./src/schemas/academy.json'),
        },


        // Provide a default set of Imgix image transformations applied to
        // Imgix-backed gatsby-image fields. These options will override the
        // defaults set by Prismic.
        // See: https://docs.imgix.com/apis/url
        imageImgixParams: {
          auto: 'compress,format',
          fit: 'max',
          q: 100,
        },

        // Provide a default set of Imgix image transformations applied to
        // the placeholder images of Imgix-backed gatsby-image fields. These
        // parameters will be applied over those provided in the above
        // `imageImgixParams` option.
        // See: https://docs.imgix.com/apis/url
        imagePlaceholderImgixParams: {
          w: 100,
          blur: 15,
          q: 50,
        },
      },
    },
    {
      resolve: 'gatsby-plugin-prismic-previews',
      options: {
        repositoryName: process.env.GATSBY_PRISMIC_REPO_NAME,
        accessToken: process.env.PRISMIC_ACCESS_TOKEN,
        path: '/preview',

        // Provide a default set of Imgix image transformations applied to
        // Imgix-backed gatsby-image fields. These options will override the
        // defaults set by Prismic.
        // See: https://docs.imgix.com/apis/url
        imageImgixParams: {
          auto: 'compress,format',
          fit: 'max',
          q: 100,
        },
        // Provide a default set of Imgix image transformations applied to
        // the placeholder images of Imgix-backed gatsby-image fields. These
        // parameters will be applied over those provided in the above
        // `imageImgixParams` option.
        // See: https://docs.imgix.com/apis/url
        imagePlaceholderImgixParams: {
          w: 100,
          blur: 15,
          q: 50,
        },
      },
    },

Thanks in advance for your help.

Hey @digital3, have you correctly set up your 404 page?
Please remember to add the same config in gatsby-ssr.js as in gatsby-browser.js.

Hy @Pau,
I've set same configuration for gatsby-browser.js and gatsby.ssr.js.
My 404 template is the ErrorPage.js

I've create a page with unique id == 404 in Prismic and tagged it with error
Look at this line of my gatsby-node.js

let template = path.resolve(__dirname, 'src/templates/HomePage.js');
      if(page.tags.includes('error')) {
         template = path.resolve(__dirname, 'src/templates/ErrorPage.js');
      }

Thanks, Denis.

If you want you can send us the URL of your project code (GitHub) and the instructions to run it to try and debug the project on our end.

Hey @digital3, thanks for the details. We just ran your project and were able to see a few errors related to the setup. For example, there's a plugin option that isn't accepted in the previews plugin: path: '/preview', and the components in the 404 page only need to have the repositoryConfigs[n].componentResolver from withPrismicUnpublishedPreview. You don't need to use the entire query there.

I can suggest you take a look at the warnings in the console when you run npm start.

You can take a look at how the preview setup is built in one of our sample projects and use the same in your project: