Preview in next.js with redirect loop

Hello!

I'm facing an issue with infinite redirects using Prismic Preview.
When accessing the page with clean cookies (no preview enabled), the site works normally.
But when I use the preview from the Prismic Dashboard, the site opens and multiple redirects happen.

It looks like the page opens, assumes it's a preview, redirects to the preview page, and then enters a redirect loop.

This problem only occurs after the project build (yarn build && yarn start). In development mode (yarn dev) the redirect problem doesn't happen and the preview works normally.

I'm using:

"@prismicio/client": "^6.4.3",
"@prismicio/helpers": "^2.3.0",
"@prismicio/react": "^2.3.0",
"@prismicio/next": "^0.1.2",
"next": "^12.1.5",
"react": "^17.0.2",

The /api/preview.tsx route is as per the documentation:

export default async function handle(
  req: NextApiRequest,
  res: NextApiResponse,
): Promise<void> {
  const businessUnit: BusinessUnitType = getBusinessUnitFromUrl(
    String(req.headers.host),
  );
  const settings = getServerConfig(businessUnit);
  const { url, accessToken } = settings.prismic;

  const client = getPrismicClient(url, accessToken, req);
  setPreviewData({ req, res });
  await redirectToPreviewURL({ req, res, client, linkResolver, defaultURL: '/' });
}

And in _app.tsx:

<PrismicPreview
            repositoryName={repositoryName}
>
...
</PrismicPreview>

I'm using getServerSideProps in pages.

1 Like

Hello @matheus.rufino

Welcome to the Prismic community, and thanks for reaching out to us.

I noticed that you don't need to add defaultURL: '/'. Could you please paste the following:

  1. package.json file
  2. prismic.js file

Thanks,
Priyanka

Hello @Priyanka, thanks for reply.

Yes:

package.json

   "dependencies": {
    "@fingerprintjs/fingerprintjs": "3.3.3",
    "@grupoboticario/flora": "^0.15.0",
    "@grupoboticario/flora-react": "^0.10.13",
    "@grupoboticario/flora-screenrecording-addon": "^2.3.0",
    "@hookform/resolvers": "^2.8.8",
    "@loadable/component": "^5.15.2",
    "@newrelic/next": "^0.1.1",
    "@prismicio/client": "^6.4.3",
    "@prismicio/helpers": "^2.3.0",
    "@prismicio/react": "^2.3.0",
    "@prismicio/next": "^0.1.2",
    "@radix-ui/react-collapsible": "^0.1.6",
    "axios": "^0.26.1",
    "cache-manager": "^3.6.1",
    "newrelic": "^8.9.1",
    "next": "^12.1.5",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-google-login": "^5.2.2",
    "react-hook-form": "^7.30.0",
    "react-icons": "^4.3.1",
    "react-loadable-visibility": "^3.0.2",
    "react-swipeable": "^6.2.1",
    "sitemap": "^7.1.1",
    "tldts": "^5.7.76",
    "uuid": "^8.3.2",
    "winston": "^3.7.2",
    "yup": "^0.32.11"
  },
  "devDependencies": {
    "@prismicio/types": "^0.1.27",
    "@release-it/bumper": "^3.0.1",
    "@testing-library/dom": "^8.13.0",
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^12.1.5",
    "@testing-library/react-hooks": "^8.0.0",
    "@testing-library/user-event": "^14.1.1",
    "@types/cache-manager": "^3.4.3",
    "@types/jest": "^27.4.1",
    "@types/jest-when": "^3.5.0",
    "@types/newrelic": "^7.0.3",
    "@types/node": "^17.0.24",
    "@types/react": "^18.0.5",
    "@types/react-loadable-visibility": "^3.0.2",
    "@types/uuid": "^8.3.4",
    "@typescript-eslint/eslint-plugin": "^5.19.0",
    "@typescript-eslint/parser": "^5.20.0",
    "canvas": "^2.9.1",
    "eslint": "<9.0.0",
    "eslint-config-next": "^12.1.5",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-jest": "^26.1.4",
    "eslint-plugin-jsx-a11y": "^6.5.1",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.29.4",
    "eslint-plugin-react-hooks": "^4.4.0",
    "eslint-plugin-simple-import-sort": "^7.0.0",
    "eslint-plugin-sonarjs": "^0.13.0",
    "husky": "^7.0.4",
    "jest": "^27.5.1",
    "jest-axe": "^6.0.0",
    "jest-watch-typeahead": "^1.0.0",
    "jest-when": "^3.5.1",
    "lint-staged": "^12.3.8",
    "marked": "^4.0.14",
    "news-fragments": "1.14.2",
    "nock": "^13.2.4",
    "node-mocks-http": "^1.11.0",
    "prettier": "^2.6.2",
    "release-it": "^14.14.2",
    "slice-machine-ui": "^0.3.7",
    "std-mocks": "^1.0.1",
    "typescript": "4.5.5"
  }

prismic.js

import * as prismic from '@prismicio/client';

import sm from './sm.json';

export const endpoint = sm.apiEndpoint;
export const repositoryName = prismic.getRepositoryName(endpoint);

export function linkResolver(doc) {
  switch (doc.type) {
    case 'home':
      return '/';
    case 'lp':
      return `/${doc.uid}`;
    case 'signup':
      return '/inscricao';
    case 'already-signup':
      return '/status/cadastro-existente';
    case 'success':
      return '/status/sucesso';
    default:
      return '/';
  }
}

We did not use the client in this same file as we had already implemented this configuration so that our token would remain private (not exposed) from use in SSR.
However, we did tests also implementing the client from prismic.js, as per the documentation and the problem remains, so I believe something else could be causing this loop of redirects :frowning:

Here is our client implementation:

import * as prismic from '@prismicio/client';
import { enableAutoPreviews } from '@prismicio/next';

export function getPrismicClient(
  apiUrl: string,
  accessToken: string,
  config: any,
) {
  const client = prismic.createClient(apiUrl, {
    ...config,
    accessToken,
  });

    enableAutoPreviews({
      client,
      ...config,
    });

  return client;
}

And our searches for content are done through predicates:

const prismicClient = getPrismicClient(
        this.apiUrl,
        this.accessToken,
        this.previewData,
      );
const tag = multibrand ? 'multimarca' : businessUnit;
const { results } = await prismicClient.get({
        predicates: [
          prismic.predicate.at(predicatePath, predicateValue),
          prismic.predicate.any('document.tags', [tag]),
        ],
      });

@Priyanka Sorry for the inconvenience, do we have any news on this? I would like to finish implementing Prismic Preview in the project I am working on.

Thank you very much!

Hi @matheus.rufino,

I work on the @prismicio/next package. Others have run into the same infinite looping issue, and I have not been able to reproduce it on my site yet. If you would like to see what others have posted, as well as some context on what @prismicio/next is doing in the background, see this GitHub issue: Preview causing infinite reloading loop in Chrome and Firefox · Issue #13 · prismicio/prismic-next · GitHub

Based on the code you shared here, could you try the following?

/api/preview.tsx

It looks like this line needs to be changed based on your getPrismicClient() function:

- const client = getPrismicClient(url, accessToken, req);
+ const client = getPrismicClient(url, accessToken, { req }); 

enableAutoPreviews() needs the req object as a single object passed as the req option.

Verify the preview resolver endpoint

Could you check that your Prismic preview resolver URL is configured to use /api/preview rather than /preview?


If these changes don't fix the issue, it would be helpful if you could share your project's code and the name of your Prismic repository. With that, I can test the preview and investigate what is causing the preview session to get stuck in a redirect loop.

Thank you!

2 Likes

Hi @angeloashmore, thanks for your reply!

I checked here and the preview route is set to /api/preview. I changed it to use { req } in getPrismicClient but the result didn't change.

The repository name is madmax-front.
What part of the code do you need me to share?

Thank you!

A while ago, I had the same problem, happening only in production and for a few custom types. After a long road of debugging, I found that using Promise.any was causing the problem:

import * as prismic from '@prismicio/client'

const BLOG_CUSTOM_TYPES = ['article', 'category']

export class PrismicClient {
  constructor(
    private client: prismic.Client = prismic.createClient(Prismic.repoName),
  ) {}

  public async getBlogDocument({
    slug,
    product,
  }: {
    slug: string
    product: Product
  }) {
    return Promise.any<BlogPostDocument | CategoryDocument>(
      BLOG_CUSTOM_TYPES.map((type) => {
        return this.client.getFirst({
          predicates: [
            prismic.predicate.at(`my.${type}.slug`, slug),
            prismic.predicate.at(`my.${type}.product`, product),
          ],
        })
      }),
    )
  }
}

Just to clarify why I need this. The URL for category and article are the same, so I have to search for that slug in both custom types and find the document I'm looking for. That's why Promise.any is perfect for me. Any request that ends successfully is the one that I'm looking for.

Whenever this function is called using the preview data, the preview gets stuck in this infinite loop, but only on production (using Netlify), so I thought Netlify could be the problem. So I moved my site to Vercel, and I had to downgrade the node version, which made me remove the Promise.any, and the problem was fixed. When I removed the Promise.any on Netlify, the problem was fixed as well:

import * as prismic from '@prismicio/client'

const BLOG_CUSTOM_TYPES = ['article', 'category']

export class PrismicClient {
  constructor(
    private client: prismic.Client = prismic.createClient(Prismic.repoName),
  ) {}

  public async getBlogDocument({
    slug,
    product,
  }: {
    slug: string
    product: Product
  }) {
    // We should use `Promise.any` here, but for some reason it
    // breaks the preview on production of BlogPost pages on Prismic
    return Promise.all<BlogPostDocument | CategoryDocument>(
      BLOG_CUSTOM_TYPES.map(async (type) => {
        return this.client
          .getFirst({
            predicates: [
              prismic.predicate.at(`my.${type}.slug`, slug),
              prismic.predicate.at(`my.${type}.product`, product),
            ],
          })
          .catch(() => {
            return Promise.resolve(null) as Promise<any>
          })
      }),
    ).then((res) => {
      const doc = res.find((result) => result)

      if (!doc) {
        return null
      }

      return doc
    })
  }
}

@klynger Thanks for your contribution!

Here in the project we don't have any promise.any / promise.all :frowning:

I just did some more tests and realized that the problem is apparently happening because of the domain.

I changed the system hosts to include a local domain (mysub.mydomain.local:3000) and multiple redirects happen on it.
When using localhost:3000 these redirects do not happen.
Does this behavior make any sense, @angeloashmore ? Maybe I'm not seeing something.

This is happening in the local environment (after doing the build) and also in the homologation/productive environment.

Basically any domain other than http://localhost:3000 the redirect loop happens.

@matheus.rufino what is your node version?

@klynger By default 16.14.2, but I also tested with:

v12.22.12
v15.8.0

I all have the same behavior :frowning:

Same problem here (for both new pages and edits). Previews are causing redirects to 404 and keeps reloading the page. The toolbar pops up and then the page will reload.

// package.json

 "dependencies": {
    "@prismicio/client": "^6.4.2",
    "@prismicio/helpers": "^2.2.1",
    "@prismicio/next": "^0.1.2",
    "@prismicio/react": "^2.3.0",
    "@prismicio/slice-simulator-react": "^0.2.1",
    "next": "12.1.5",
    "react": "18.1.0",
    "react-dom": "18.1.0"
  },
// prismicio.js

export const endpoint = sm.apiEndpoint
export const repositoryName = prismic.getRepositoryName(endpoint)

export function linkResolver(doc) {
  switch (doc.type) {
    case 'home':
      return '/'
    case 'page':
      return `/${doc.uid}`
    default:
      return null
  }
}
export function createClient(config = {}) {
  const client = prismic.createClient(endpoint, {
    ...config,
  })

  enableAutoPreviews({
    client,
    previewData: config.previewData,
    req: config.req,
  })

  return client
}
// _app.js

export default function App({ Component, pageProps }) {
  return (
    <PrismicProvider
      linkResolver={linkResolver}
      internalLinkComponent={({ href, children, ...props }) => (
        <Link href={href}>
          <a {...props}>
            {children}
          </a>
        </Link>
      )}
    >
      <PrismicPreview repositoryName={repositoryName}>
        <Component {...pageProps} />
      </PrismicPreview>
    </PrismicProvider>
  )
}
// [[...uid]].js

export async function getStaticProps({ params, locale, previewData }) {
  const uid = params?.uid?.[params.uid?.length - 1] || 'home'
  const client = createClient({ previewData })

  try {
    const navigation = await client.getSingle('navigation', { lang: locale })
    const page = await client.getByUID('page', uid, { lang: locale })

    return {
      props: { page, navigation },
    }
  } catch (error) {
    return {
      notFound: true
    }
  }
}

export async function getStaticPaths() {
  return {
    paths: [],
    fallback: true,
  }
}

Catch response: PrismicError: No documents were returned

Without preview everything is working just fine.

@angeloashmore I sent you in private a repository with details to reproduce the problem, can we proceed in this way?

@matheus.rufino Apologies for the delay. I received your repository invitation on GitHub. I will take a look at it tomorrow and let you know what I find. Thank you for sending that over. :slight_smile:

1 Like

@matheus.rufino I wasn't able to reproduce the issue on my side with your repository, but I was able to trigger multiple refreshes in my own sample project.

After digging into the issue further, it looks like the issue is not with @prismicio/next, but potentially with the Prismic Toolbar instead. The toolbar is necessary to have complete support for previews to work in Next.js, so we can't just remove it.

I am having a hard time finding the cause since I'm not sure what triggers it. I noticed the io.prismic.preview cookie can get into a state where it is not removable. In this state, even removing it manually via Chrome's Application tab does not work.

However, I found that clearing all of my Prismic cookies seemed to resolve the issue.

I will continue trying to find the cause, but in the meantime, could you clear all of your Prismic cookies and try again? By all Prismic cookies, I mean all of them across all domains, not just the io.prismic.preview cookie.

If you are using Chrome, you can do this following these instructions: Clear, enable, and manage cookies in Chrome - Computer - Google Chrome Help. When searching in the list of cookies, search "prismic".

Thanks!

@angeloashmore Even clearing the cookies I still have the problem. Was it possible to test the repository project using a domain other than http://localhost:3000?

But I understand about the toolbar, it is possible that it is in my case too, although clearing the cookies does not stop the problem.
Is there any way I can help investigate the toolbar?

Thank you!

@matheus.rufino Thanks for trying that out. I have not tested your repository deployed, but I will do so.

I have identified the source of an issue in the Prismic Toolbar that can trigger infinite refreshes. This issue may be what you are experiencing.

You can see the PR here: feat: prevent infinite refreshes and exiting race condition by angeloashmore · Pull Request #97 · prismicio/prismic-toolbar · GitHub

I will work toward getting the fix merged and deployed and will post here again once it is something you can test. Thanks for your patience!

1 Like

Thanks for the update.
I'm following along to test as soon as a release is available.

@angeloashmore Hello!
I saw that the PR was approved but not yet merged. Is there an update schedule I can follow to see when PR closes?
Thank you!