Issue with Next.js preview API route not working on Vercel deployment

21 replies
Last updated: Jun 9, 2022
My /api/preview.js returns 404 on remote site only (works on localhost).
I know this isn't directly Sanity related but I'm hoping someone has come across this issue running Next / Sanity on Vercel. I've checked the CORS origin, the URL is there
with credentials, same as localhost. Attaching my Vercel build output in a thread.
AI Update

This is a classic Next.js deployment issue that catches a lot of people! The problem is almost certainly that you're using next export (static export) in your build configuration, which disables all API routes including /api/preview.js.

When you run next export, Next.js generates a completely static site with no server-side functionality. API routes require a Node.js server to run, so they simply won't exist in the exported build. This works on localhost because next dev always runs a server, but fails on Vercel when you're doing a static export.

To fix this:

  1. Remove next export from your build process. Check your package.json build script and make sure it's just next build, not next build && next export

  2. Don't set output: 'export' in your next.config.js - this does the same thing as next export

  3. Deploy as a hybrid Next.js app on Vercel (the default). Vercel fully supports Next.js API routes out of the box when you're not forcing a static export

Your CORS settings are fine - that's not the issue here. The API route literally doesn't exist in your deployed build because static exports strip them out, as mentioned in the Next.js documentation on static exports.

Modern alternative: If you're using preview mode for Sanity content, consider using Draft Mode (the newer Next.js 13+ approach) or Sanity's Live Content API which provides real-time previews without requiring API routes at all. The Live Content API works with static sites and integrates directly with Next.js through framework loaders.

But for your immediate issue: check your Vercel build output and configuration for anything related to static export and remove it. Your API routes will work once Next.js is deployed as a proper hybrid app rather than a static export.

[08:23:05.343] Cloning <http://github.com/shadowbrand/shinecapital-next|github.com/shadowbrand/shinecapital-next> (Branch: develop, Commit: 006c3fd)
[08:23:05.913] Cloning completed: 570.535ms
[08:23:06.361] Installing build runtime...
[08:23:08.307] Build runtime installed: 1.946s
[08:23:08.939] Looking up build cache...
[08:23:09.413] Build Cache not found
[08:23:09.611] Installing dependencies...
[08:23:09.853] yarn install v1.22.17
[08:23:09.909] [1/4] Resolving packages...
[08:23:10.073] [2/4] Fetching packages...
[08:23:23.886] [3/4] Linking dependencies...
[08:23:23.888] warning " > next@12.0.7" has incorrect peer dependency "react@^17.0.2 || ^18.0.0-0".
[08:23:23.888] warning " > next@12.0.7" has incorrect peer dependency "react-dom@^17.0.2 || ^18.0.0-0".
[08:23:23.888] warning "next > @next/react-dev-overlay@12.0.7" has incorrect peer dependency "react@^17.0.2".
[08:23:23.889] warning "next > @next/react-dev-overlay@12.0.7" has incorrect peer dependency "react-dom@^17.0.2".
[08:23:23.889] warning "next > styled-jsx > @babel/plugin-syntax-jsx@7.14.5" has unmet peer dependency "@babel/core@^7.0.0-0".
[08:23:23.890] warning " > react-ga@3.3.0" has unmet peer dependency "prop-types@^15.6.0".
[08:23:23.892] warning " > styled-components@5.3.3" has unmet peer dependency "react-is@>= 16.8.0".
[08:23:30.691] [4/4] Building fresh packages...
[08:23:30.929] success Saved lockfile.
[08:23:30.940] Done in 21.09s.
[08:23:30.960] Detected Next.js version: 12.0.7
[08:23:30.962] Running "yarn run build"
[08:23:31.234] yarn run v1.22.17
[08:23:31.258] $ next build && next export
[08:23:31.906] Attention: Next.js now collects completely anonymous telemetry regarding usage.
[08:23:31.907] This information is used to shape Next.js' roadmap and prioritize features.
[08:23:31.907] You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
[08:23:31.907] <https://nextjs.org/telemetry>
[08:23:31.907] 
[08:23:32.023] info  - Checking validity of types...
[08:23:32.151] warn  - No ESLint configuration detected. Run next lint to begin setup
[08:23:32.155] info  - Creating an optimized production build...
[08:23:32.176] info  - Disabled SWC as replacement for Babel because of custom Babel configuration ".babelrc" <https://nextjs.org/docs/messages/swc-disabled>
[08:23:32.721] info  - Using external babel configuration from /vercel/path0/.babelrc
[08:23:46.272] info  - Compiled successfully
[08:23:46.272] info  - Collecting page data...
[08:23:48.862] info  - Generating static pages (0/12)
[08:23:49.168] info  - Generating static pages (3/12)
[08:23:49.486] info  - Generating static pages (6/12)
[08:23:49.809] info  - Generating static pages (9/12)
[08:23:50.167] info  - Generating static pages (12/12)
[08:23:50.184] info  - Finalizing page optimization...
[08:23:50.189] 
[08:23:50.207] Page                                       Size     First Load JS
[08:23:50.207] ┌ ● /                                      2.54 kB         103 kB
[08:23:50.208] ├   /_app                                  0 B            87.3 kB
[08:23:50.208] ├ ○ /404                                   340 B          87.6 kB
[08:23:50.208] ├ ● /500 (304 ms)                          340 B          87.6 kB
[08:23:50.208] ├ ● /about                                 3.41 kB         114 kB
[08:23:50.208] ├ λ /api/exit-preview                      0 B            87.3 kB
[08:23:50.208] ├ λ /api/preview                           0 B            87.3 kB
[08:23:50.208] ├ ● /portfolio (325 ms)                    865 B           117 kB
[08:23:50.209] ├ ● /preview/[slug] (1873 ms)              2.04 kB         112 kB
[08:23:50.209] ├   ├ /preview/portfolio (357 ms)
[08:23:50.209] ├   ├ /preview/blue (347 ms)
[08:23:50.209] ├   ├ /preview/rainbow (310 ms)
[08:23:50.210] ├   ├ /preview/green (303 ms)
[08:23:50.210] ├   ├ /preview/about
[08:23:50.210] ├   └ /preview/team
[08:23:50.210] └ ● /team                                  3.46 kB         114 kB
[08:23:50.210] + First Load JS shared by all              87.3 kB
[08:23:50.211]   ├ chunks/framework-ddde1c8153ec7431.js   40.6 kB
[08:23:50.211]   ├ chunks/main-d36f011b027ca1e1.js        26.9 kB
[08:23:50.211]   ├ chunks/pages/_app-2a5cf2a1a7c7b089.js  18.2 kB
[08:23:50.211]   └ chunks/webpack-7e69b6628b5576da.js     1.72 kB
[08:23:50.211] 
[08:23:50.211] λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
[08:23:50.211] ○  (Static)  automatically rendered as static HTML (uses no initial props)
[08:23:50.211] ●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
[08:23:50.212] 
[08:23:50.870] info  - using build directory: /vercel/path0/.next
[08:23:50.875] info  - Copying "static build" directory
[08:23:50.878] info  - No "exportPathMap" found in "/vercel/path0/next.config.js". Generating map from "./pages"
[08:23:50.879] info  - Launching 3 workers
[08:23:50.880] warn  - Statically exporting a Next.js application via `next export` disables API routes.
[08:23:50.880] This command is meant for static-only hosts, and is not necessary to make your application static.
[08:23:50.880] Pages in your application without server-side data dependencies will be automatically statically exported by `next build`, including pages powered by `getStaticProps`.
[08:23:50.880] Learn more: <https://nextjs.org/docs/messages/api-routes-static-export>
[08:23:50.880] info  - Exporting (0/6)
[08:23:50.880] info  - Copying "public" directory
[08:23:51.151] info  - Exporting (1/6)
[08:23:51.154] info  - Exporting (2/6)
[08:23:51.158] info  - Exporting (4/6)
[08:23:51.200] info  - Exporting (6/6)
[08:23:51.209] Export successful. Files written to /vercel/path0/out
[08:23:51.213] Done in 19.98s.
[08:23:52.857] Generated build outputs:
[08:23:52.858]  - Static files: 73
[08:23:52.858]  - Serverless Functions: 0
[08:23:52.858]  - Edge Functions: 0
[08:23:52.858] Deployed outputs in 1s
[08:23:55.605] Build completed. Populating build cache...
[08:24:04.204] Uploading build cache [39.56 MB]...
[08:24:06.050] Build cache uploaded: 1.846s
[08:24:06.073] Done with "package.json"
Your build passes without an error, so there isn’t much to see here. What does your preview API route looks like?
Hi Kitty! Here's my pages/api/preview.js:

// ./web/pages/api/preview.js

export default function preview(req, res) {
  console.log(req, res)
  if (!req?.query?.secret) {
    return res.status(401).json({message: 'No secret token'})
  }

  // Check the secret and next parameters
  // This secret should only be known to this API route and the CMS
  if (req.query.secret !== process.env.SANITY_PREVIEW_SECRET) {
    return res.status(401).json({message: 'Invalid secret token'})
  }

  if (!req.query.slug) {
    return res.status(401).json({message: 'No slug'})
  }

  // Enable Preview Mode by setting the cookies
  res.setPreviewData({})

  // Redirect to the path from the fetched post
  // We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
  res.writeHead(307, {Location: `/preview/${req?.query?.slug}` ?? `/`})

  return res.end()
}
When I click Open Preview, Sanity leads me to https://www.shine.vc/api/preview?secret=[thesecret]&amp;slug=[theslug] (removed thesecret and theslug here) returning Error 404. The redirect never happens.
Alright, interesting. So that route has not been deployed at all it seems.
Any idea why that is? I had node version 16x, tried with 14x but no success...
No, that’s not a version problem. Did you add that route recently? Are you sure it was properly deployed to production since?
I don't know if it was properly deployed – I've tried multiple times though. How can I check?
Where do you host your site? Vercel, right? Can you check the Vercel logs to make sure your production build passed?
It definitely passed. The first code block I pasted is the log from deploying.I wonder if it has to do with that warning in yellow (see screenshot for additional formatting)
See how the api/ routes have this lambda sign?
Ooooh, are you using
next export
?
I guess so! Help
Right, so
next export
creates a bunch of static files. API routes of course do not work with that because they need to run on a lambda or Node server or whatnot. They’re basically server-side code so they can’t be statically generated. So you’ll need to update your deployment process to use
next build
instead of
next export
. 🙂
!!! Thank you! I'll try that right now 🙂
Gosh – I'm so glad I asked. Thank you!!!
Works? 🙂
It works. Sending you flowers!
Yay! 💚
And it isn't even a Sanity issue – so really, thank you so much! Saved me days of nightmares.
Happy to help. 😊
Glad you found the problem!

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?