UnlyEd/next-right-now

View on GitHub
src/pages/api/preview.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { logEvent } from '@/modules/core/amplitude/amplitudeServerClient';
import {
  AMPLITUDE_API_ENDPOINTS,
  AMPLITUDE_EVENTS,
} from '@/modules/core/amplitude/events';
import { filterExternalAbsoluteUrl } from '@/modules/core/js/url';
import { createLogger } from '@/modules/core/logging/logger';
import { configureReq } from '@/modules/core/sentry/server';
import { flushSafe } from '@/modules/core/sentry/universal';
import * as Sentry from '@sentry/node';
import appendQueryParameter from 'append-query';
import {
  NextApiRequest,
  NextApiResponse,
} from 'next';

const fileLabel = 'api/preview';
const logger = createLogger({
  fileLabel,
});

/**
 * Key to use in order to force disable "auto preview mode".
 *
 * If a page is loaded with noAutoPreviewMode=true in query parameter, then it won't try to enable the Preview mode, even if it's disabled.
 *
 * @example ?noAutoPreviewMode=true
 */
export const NO_AUTO_PREVIEW_MODE_KEY = 'noAutoPreviewMode';

type EndpointRequestQuery = {
  /**
   * Whether to start/stop the Preview Mode.
   *
   * @example ?stop=true Will stop the preview mode.
   * @example ?stop=false Will start the preview mode.
   * @default ?stop=false
   */
  stop: string;

  /**
   * Url to redirect to once the preview mode has been started/stopped.
   *
   * @example ?redirectTo=/en
   * @example ?redirectTo=/fr/solutions
   * @default ?redirectTo=/
   */
  redirectTo: string;
}

type EndpointRequest = NextApiRequest & {
  query: EndpointRequestQuery;
};

/**
 * Preview Mode API.
 *
 * Enables and disables preview mode.
 *
 * The official example uses a security token to enable the preview mode, we don't.
 * This is a choice, as we don't need/want to protect our preview mode.
 * Protecting the preview mode makes most sense when this mode can be used in production, so that you can preview content served by Next.js from a CMS/tool of your choice.
 * Thus, it's strongly related to how you're planning on using it, and we decided to keep it simpler, by not using any kind of security.
 *
 * @param req
 * @param res
 * @method GET
 *
 * @see https://nextjs.org/docs/advanced-features/preview-mode#step-1-create-and-access-a-preview-api-route
 * @see https://nextjs.org/docs/advanced-features/preview-mode#clear-the-preview-mode-cookies
 */
export const preview = async (req: EndpointRequest, res: NextApiResponse): Promise<void> => {
  try {
    configureReq(req, { fileLabel });

    await logEvent(AMPLITUDE_EVENTS.API_INVOKED, null, {
      apiEndpoint: AMPLITUDE_API_ENDPOINTS.PREVIEW,
    });

    const {
      stop = 'false',
      redirectTo = '/',
    }: EndpointRequestQuery = req.query;
    // Add NO_AUTO_PREVIEW_MODE_KEY parameter to query, to avoid running into infinite loops if the Preview mode couldn't start
    // Useful when the cookie created by Next.js cannot be written (Incognito mode)
    const safeRedirectUrl = appendQueryParameter(filterExternalAbsoluteUrl(redirectTo as string), `${NO_AUTO_PREVIEW_MODE_KEY}=true`);

    // XXX We don't want to enable preview mode for the production stage, it's only allowed for non-production stages
    //  It's allowed during development for testing purpose
    //  It's allowed during staging because this stage is being used as a "preview environment"
    if (process.env.NEXT_PUBLIC_APP_STAGE !== 'production') {
      if (stop === 'true') {
        res.clearPreviewData();

        logger.info('Preview mode stopped');
      } else {
        res.setPreviewData({});

        logger.info('Preview mode enabled');
      }
    } else {
      logger.error('Preview mode is not allowed in production');
      Sentry.captureMessage('Preview mode is not allowed in production', Sentry.Severity.Warning);
    }

    await flushSafe();

    res.writeHead(307, { Location: safeRedirectUrl });
    res.end();
  } catch (e) {
    Sentry.captureException(e);
    logger.error(e.message);

    await flushSafe();

    res.json({
      error: true,
      message: process.env.NEXT_PUBLIC_APP_STAGE === 'production' ? undefined : e.message,
    });
  }
};

export default preview;