withastro / adapters

Home for Astro's core maintained adapters
69 stars 41 forks source link

Cloudflare adapter with "hybrid" output error on prerendering static routes #427

Closed marceloverdijk closed 3 weeks ago

marceloverdijk commented 3 weeks ago

Astro Info

Astro                    v4.16.7
Node                     v20.12.1
System                   macOS (arm64)
Package Manager          npm
Output                   hybrid
Adapter                  @astrojs/cloudflare
Integrations             none

Describe the Bug

I'm trying to build a Astro project with hybrid output to be deployed on Cloudflare but are facing problems on prerendering the static routes.

As Minimal Reproducible Example I created a minimalistic example project which is not the actual project I'm working on.

The server pages will use Cloudflare D1, but for simplicity the server route (/pages/dynamic) is just a page with some text.

The statically rendered pages (/users/[id].astro) are generated with getStaticPaths.

The static paths are retrieved from a SQLite database included as (static) asset in the project (/prisma/dev.db). This is meant as static asset, and it should not be using D1. That SQLite database should only be used during prerendering the static routes. Note it uses Prisma to query this local SQLite database.

However, when running npm run astro build this results in a error:

❯ npm run build

> astro-cloudflare-hybrid@0.0.1 build
> astro check && astro build

22:08:05 [WARN] The currently selected adapter `@astrojs/cloudflare` is not compatible with the image service "Sharp".
22:08:05 [WARN] [config] The adapter @astrojs/cloudflare provides experimental support for "astro:env getSecret". You may experience issues or breaking changes until this feature is fully supported by the adapter.
22:08:05 [types] Generated 24ms
22:08:05 [check] Getting diagnostics for Astro files in /Users/marceloverdijk/workspace/astro-cloudflare-hybrid...
src/pages/users/[id].astro:22:7 - warning ts(6133): 'id' is declared but its value is never read.

22 const { id } = Astro.params
         ~~~~~~

Result (8 files): 
- 0 errors
- 0 warnings
- 1 hint

22:08:07 [WARN] The currently selected adapter `@astrojs/cloudflare` is not compatible with the image service "Sharp".
22:08:07 [WARN] [config] The adapter @astrojs/cloudflare provides experimental support for "astro:env getSecret". You may experience issues or breaking changes until this feature is fully supported by the adapter.
22:08:07 [types] Generated 20ms
22:08:07 [build] output: "hybrid"
22:08:07 [build] directory: /Users/marceloverdijk/workspace/astro-cloudflare-hybrid/dist/
22:08:07 [build] adapter: @astrojs/cloudflare
22:08:07 [build] Collecting build info...
22:08:07 [build] ✓ Completed in 27ms.
22:08:07 [build] Building hybrid entrypoints...
".prisma/client/default" is imported by ".prisma/client/default?commonjs-external", but could not be resolved – treating it as an external dependency.
22:08:07 [vite] ✓ built in 336ms
22:08:07 [build] ✓ Completed in 347ms.

 prerendering static routes 
Invalid module ".prisma/client/default" is not a valid package name imported from /Users/marceloverdijk/workspace/astro-cloudflare-hybrid/dist/_worker.js/pages/users/_id_.astro.mjs
  Stack trace:
    at parsePackageName (node:internal/modules/esm/resolve:782:11)
    at moduleResolve (node:internal/modules/esm/resolve:927:18)
    at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:390:12)
    at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:234:38)
    at link (node:internal/modules/esm/module_job:86:36)

When I remove the Cloudflare adapter and change the server rendered page (/pages/dynamic) to prerendered as well, I can successfully npm run build the project. This can be checked in the pure_static_without_cf_adapter branch.

In my actual project I get a different error, but I assume the error is the same. My actual project uses multiple Prisma clients so there is no default client. The error message I get there is:

22:29:53 [ERROR] [build] Failed to call getStaticPaths for src/pages/products/[id].astro
PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `Node.js`).

I understand when using the Cloudflare adapter the server routes need to be compatible with the Cloudflare runtime environment (workerd), but for the prerendered routes it seems to use that same runtime.

What's the expected result?

Prerendered pages should not be restricted to the Cloudflare runtime (workerd).

This restriction limits the possibilities of a Astro hybrid approach.

E.g. in my case 95% of the pages are static pages rendered from a local SQLite database. The other 5% (mainly account pages) and some API endpoints should be server side.

Is there an alternative solution available? E.g. first rendering the static pages, without the Cloudflare adapter?

Link to Minimal Reproducible Example

https://github.com/marceloverdijk/astro-cloudflare-hybrid

Participation

marceloverdijk commented 3 weeks ago

Related Discord discussion: https://discord.com/channels/830184174198718474/1298551479476555786/1298551479476555786

ematipico commented 3 weeks ago

@marceloverdijk Have you tried to address the error emitted from Prisma? It doesn't seem related to Astro

marceloverdijk commented 3 weeks ago

It only occurs with the Cloudflare adapter enabled, otherwise not. With this adapter Astro does not run in a Node runtime.

alexanderniebuhr commented 3 weeks ago

Reading through the discussion on Discord, this is nothing we can fix right now on the adapters side. Pre-rendering uses a Node.Js runtime so it would work with Prisma, like you described. However as @Fryuni already said, we use the same bundle for SSR & SSG. I'm not sure how Prisma does check for the runtime, the error you see comes from Prisma, but you can workaround this.

If you however can make sure you don't use Prisma in any SSR page, you can also customize the vite settings to bundle Prisma for a Node.js enviroment, just override the settings: https://docs.astro.build/en/reference/configuration-reference/#vite

However we can't add any of those settings by default, because not every user would only use it for SSG pages.

I think using Server Islands could also be a way around the issue, at least I hope.

marceloverdijk commented 3 weeks ago

Thx @alexanderniebuhr !

I can (and want to) make sure no SSR page will use this Prisma Client. I wasn't aware of this Vite ssr.external configuration but your comment saved the day.

It's understood Astro can't add this by default; hence in my actual project I have a 2nd Prisma Client accessing a Cloudflare D1 database in server-side rendered pages ;-) Only for pre-rendered pages I wanted to use a local SQLite database.

For reference, here is what I did to get it working:

// @ts-check
import { defineConfig } from 'astro/config';

import cloudflare from '@astrojs/cloudflare';

// https://astro.build/config
export default defineConfig({
  output: 'hybrid',
  adapter: cloudflare(),
  vite: {
    ssr: {
      // Vite won't include the specified package in the server-side bundle during the SSR build process.
      // Instead, Vite assumes that the package will be available as an external dependency in the Node.js environment where the code runs.
      external: ['@prisma/client'],
    },
  },
});

https://github.com/marceloverdijk/astro-cloudflare-hybrid/tree/vite_ssr_external_exclude_prisma_client