kit: prerender = true in /+layout.server.ts breaks the route after deployment to netlify / vercel

Describe the bug

I found this bug in one of my bigger projects after migrating to the new routing configuration. It took me a while to pinpoint that it was the prerender = true setting in /+layout.server.ts that was causing the bug. The project runs fine locally using pnpm dev || pnpm build || pnpm preview. The project also has no problem building once deployed to Netlify / Vercel. It’s only after publishing that it breaks.

I created a new project from scratch and was able to recreate the bug when deployed to both netlify and vercel.

You’ll be able to see the 500 error on both Netlify and Vercel using the above links. I also have examples of the exact same code but with prerendering not set and the routes work as intended.

I tried to make the code and example as clear as possible, let me know if you have any questions.

EDIT: prerender = true works on normal /+page.server.ts powered routes from my testing

Reproduction

Logs

Netlify doesn't seem to log any error in the render.js function logs.

Vercel logs this: [GET] /episodes-3/__data.js?__invalid=nyn&__id=1

The error on the frontend is this:
500
Failed to fetch dynamically imported module: https://layoutprerender.vercel.app/episodes-3/__data.js?__invalid=nyn&__id=1
TypeError: Failed to fetch dynamically imported module: https://layoutprerender.vercel.app/episodes-3/__data.js?__invalid=nyn&__id=1

System Info

Netlify / Vercel

Severity

annoyance

Additional Information

Marked as an annoyance because I can just turn off prerendering for my /+layout.server.ts and the site works.

But I would like to be able to turn this feature back on since it does slow down certain routes and puts added stress on my backend.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 5
  • Comments: 31 (15 by maintainers)

Commits related to this issue

Most upvoted comments

The issue here is that src/routes/episodes-[number] is marked as prerenderable by virtue of the export const prerender = true in src/routes/episodes-[number]/+layout.server.ts, which means the code to generate the __data.js files needed by the page isn’t included in the deployment to Vercel/Netlify, on the assumption that the __data.js files were already prerendered. But they weren’t, because the crawler didn’t get to them, because / (which contains the links the crawler would otherwise follow) is not marked as prerenderable.

At the very least, we should make this bug more discoverable by omitting those routes from the manifest used during preview, so that it fails locally the same way it fails in production.

One solution could be to always render pages, whether or not they’re marked as prerenderable, so that the crawler can find prerenderable pages via non-prerenderable pages. Non-prerenderable pages would just be discarded. But this would make everyone’s builds much slower. Another (probably much more sensible) possibility would be to print a warning or error if a route was marked as prerenderable but wasn’t actually prerendered.

@s3812497 If I comment that line out I get

Using @sveltejs/adapter-static
  @sveltejs/adapter-static: all routes must be fully prerenderable (unless using the 'fallback' option — see https://github.com/sveltejs/kit/tree/master/packages/adapter-static#spa-mode). Try adding `export const prerender = true` to your root layout.js — see https://kit.svelte.dev/docs/page-options#prerender for more details

Oh wait I did have an @migration that I incorrectly fixed. I just had a export const countries = [...] in js file I was importing and moved it some +server.js file I think. I put in data.js file and imported in +page.svelte file and works. My bad

Same issue when deploying to Cloudflare pages via the adapter.

The title is misleading as the actual issue is SvelteKit does not pre-render dynamic routes marked for prerender, leading to missing html files. This only occurs when there aren’t any non-dynamic routes also marked for prerender.

Creating my own store was very simple, just had to wrap my thick head around the need to. Thanks again for your help.

If anyone is reading this wondering if we solved the original issue, we did not. @s3812497 just helped me figure out a few things.

Is it intended to overwrite the data derived from the layout as well?

Yes, the layout server load function is not re-run. The data stays the same as it was during build.

If that’s the case, creating my own session store would be the fix and I am fine with that. I think one of my hang-ups has been that I had prerendering working before the route migration with the built-in session store, so the UI stayed up to date even on prerendered routes.

Yeah, you’ll just want to mimic the same order of events:

  1. While client-side, fetch data from an endpoint (this used to run getSession() on the server and return data to the client)
  2. Keep the data in a store (hence, the $session` store).

EDIT: Sorry for cluttering the issue. The initial bug is that build does not prerender dynamic parameter routes unless a static route is also set for prerendering. See this repository for the reproduction.

  1. The prerendered pages are missing from the output after build, hence the error. (check /.svelte-kit/output/prerendered/)
  2. Using the prerender option in +page.ts and +layout.ts instead of +layout.server.ts doesn’t change the outcome.
  3. Adding a static route to prerender causes the build to properly prerender the dynamic routes as well.

Somehow the compiler is skipping the dynamic routes for prerender (although there are links referencing them).

Just as a follow up- my site was acting weird before I realized that putting prerender = true in the +layout.server.ts prevented my UI from having logged in / logged out state. So I reverted again and I will wait until the original problem is fixed.

Ok, I think adding prerender = true to the root +layout.server.ts and then specifying prerender = false on the routes I want to keep dynamic worked. I’ll keep playing with it and make sure this works for me.

Thank you for helping me!

The original bug still exists though so I’m going to leave this open.

file:///home/stephen/Projects/craftroulette/node_modules/.pnpm/@sveltejs+kit@1.0.0-next.468_qevq5tvhizyxp44gfhkn6m3ljq/node_modules/@sveltejs/kit/src/core/prerender/prerender.js:50
                                throw new Error(format_error(details, config));
                                      ^
Error: 404 /episodes/episode-[number=integer]
    at file:///home/stephen/Projects/craftroulette/node_modules/.pnpm/@sveltejs+kit@1.0.0-next.468_qevq5tvhizyxp44gfhkn6m3ljq/node_modules/@sveltejs/kit/src/core/prerender/prerender.js:50:11
    at save (file:///home/stephen/Projects/craftroulette/node_modules/.pnpm/@sveltejs+kit@1.0.0-next.468_qevq5tvhizyxp44gfhkn6m3ljq/node_modules/@sveltejs/kit/src/core/prerender/prerender.js:355:4)
    at visit (file:///home/stephen/Projects/craftroulette/node_modules/.pnpm/@sveltejs+kit@1.0.0-next.468_qevq5tvhizyxp44gfhkn6m3ljq/node_modules/@sveltejs/kit/src/core/prerender/prerender.js:232:3)

It’s weird that it can’t find the route. What’s the structure of your routes?

So a 404- I also tried ‘*’ with crawl: true and it didn’t pre-render anything.

In the docs, it mentions the '*' value for entries doesn’t include dynamic routes (must be why it ignores your routes).

The * string includes all non-dynamic routes (i.e. pages with no [parameters] ) https://kit.svelte.dev/docs/configuration#prerender

I’d recommend adding a prerender = true to your home page layout instead.

So,

prerender: {
    default: true
},

has been removed, according to the error I got during build, but is still in the docs.

and any route that I tried to specify with entries that had a parameter in the route, failed:

[vite-plugin-svelte-kit] Prerendering failed with code 1
error during build:
Error: Prerendering failed with code 1
    at ChildProcess.<anonymous> (file:///home/stephen/Projects/craftroulette/node_modules/.pnpm/@sveltejs+kit@1.0.0-next.468_qevq5tvhizyxp44gfhkn6m3ljq/node_modules/@sveltejs/kit/src/exports/vite/index.js:435:15)
    at ChildProcess.emit (node:events:513:28)
    at Process.ChildProcess._handle.onexit (node:internal/child_process:291:12)
 ELIFECYCLE  Command failed with exit code 1.

I think I was closer before but it was worth the shot.

@s3812497 thanks. I will try this.

Question- I have not used the svelte.config.js prerender option before, will this affect the pages that I already have prerender = true set? Can I set it at the config level AND the route level? The majority of the routes are set to prerender.

I’ve never used the option before either, but I would assume the entries option would be in addition to per-route configuration.

Should I just set

prerender: {
    default: true
},

and then specify all of the paths I don’t want to pre-render? Might be easier?

Sounds like the right way to go 😃

The project breaks without it. See https://github.com/sveltejs/kit/issues/6462