sveltejs / kit

web development, streamlined
https://kit.svelte.dev
MIT License
18.43k stars 1.89k forks source link

Avoid loading `+page.js`/`+layout.js` files on server in SPA mode #12580

Open janopae opened 4 weeks ago

janopae commented 4 weeks ago

Describe the bug

Even though I did everything the docs provide in order to disable any form of server side rendering or static site generation, the dev server tries to execute my code on the server.

Reproduction

npm create svelte@latest my-app
cd my-app
npm install

Edit src/routes/+layout.ts according to https://kit.svelte.dev/docs/single-page-apps

export const ssr = false;
export const prerender = false;

Edit svelte.config.js according to https://kit.svelte.dev/docs/single-page-apps#usage

import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
    // Consult https://kit.svelte.dev/docs/integrations#preprocessors
    // for more information about preprocessors
    preprocess: vitePreprocess(),

    kit: {
        // See https://kit.svelte.dev/docs/adapters for more information about adapters.
        adapter: adapter({
            fallback: 'index.html'
        }),
    }
};

Create some code that will only work on client side

// +layout.ts

localStorage.setItem('test', 'lol');

Start the dev server

npm run dev

Open the website in browser, and there will be an error complaining about localStorage not being definded.

Logs

No response

System Info

System:
    OS: Linux 5.15 Ubuntu 22.04.4 LTS 22.04.4 LTS (Jammy Jellyfish)
    CPU: (4) x64 Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz
    Memory: 870.74 MB / 1.91 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 20.5.1 - /usr/bin/node
    Yarn: 1.22.22 - /usr/bin/yarn
    npm: 9.8.0 - /usr/bin/npm
  npmPackages:
    @sveltejs/adapter-auto: ^3.0.0 => 3.2.2 
    @sveltejs/adapter-static: ^3.0.2 => 3.0.2 
    @sveltejs/kit: ^2.0.0 => 2.5.20 
    @sveltejs/vite-plugin-svelte: ^3.0.0 => 3.1.1 
    svelte: ^4.2.7 => 4.2.18 
    vite: ^5.0.3 => 5.3.5

Severity

serious, but I can work around it

Additional Information

No response

janopae commented 4 weeks ago

Update: It seems like this also stops the build process, not just the dev server.

Is there really no way to only target a browser if that's the only place your code is going to run in?

dominikg commented 4 weeks ago

please provide a link to a repository with a complete and minimal reproduction. You mention a custom adapter and the filename you use layout.ts is missing the + at the start.

janopae commented 4 weeks ago

My apologies – the custom adapter and the missing + were both a mistake. I use a custom adapter in the app I develop, but I could reproduce this with adapter-static.

I created a repository to reproduce the problem: https://github.com/janopae/reproduce-sveltekit-tries-to-run-ssr-code-even-with-ssr-disabled

janopae commented 4 weeks ago

It seems like SvelteKit has to execute the +layout.ts, otherwise it wouldn't even know that SSR is disabled.

So we need either another way to add layout code that won't be used at compile time to determine wether SSR is enabled (would be a weird twist on the naming of the file), or we need a way to disable SSR from the begin with (e. g. in the svelte.config.js).

dominikg commented 4 weeks ago

you can't use localStorage in +layout.ts without an if(browser) guard, spa mode or not.

also you have to put code in exported functions, not top level.

david-plugge commented 3 weeks ago

Im pretty sure your code works IF you put it inside a load function. The file has to be evaluated so sk can read the ssr setting but if SSR is off, the load function is not executed on the server.

janopae commented 3 weeks ago

Alright. I think this is an unexpected gotcha that could be avoided with a general ssr switch in the svelte.config.js, which would turn this issue into a feature request. But if there are reasons I don't know about why we can't have a switch in the top level configuration and you think it should stay this way, feel free to close this issue.

Personally, I reconsidered the use of SvelteKit for my use case, as there have been too many gotchas that took me a lot of time to resolve, and they keep coming, mostly relating to disabling SSR:

Especially, developing a browser extension using sk seems impossible at the moment, because both prerendered and "fallback" routing code can only be executed with the CSP setting "unsafe-inline", which can't be enabled for browser extensions (https://github.com/sveltejs/kit/issues/11009).

I'd love to see the SvelteKit homege communicating its focus on deployments with a node server, as currently, the website presents SvelteKit as "super flexible" and especially mentions SPAs without node backends as a use case.

bholmesdev commented 5 days ago

Thanks for the thorough overview @janopae. I'm early on my SPA journey with SvelteKit, and you point out some gotchas I have yet to hit.

I personally didn't reach for SvelteKit for the "typical" reasons. I wanted to use Svelte to create a client application, but there simply aren't viable CSR routers for Svelte other than SvelteKit. My use case is building a local-first application that relies on SQLite in the browser, and I use +page.ts files to execute queries against this database before displaying the page. Obviously, this cannot run in a server context. I managed to get this working client-side with a browser guard in front of my database object, but I do worry how brittle this could get overtime.

Personally, I expected a +page.client.ts file that mirrors the format of +page.server.ts. It seems odd that you can create a server-side loader and a "universal" loader but not a client loader. This reflects the vibe I got trying to build an SPA: the authors do not believe client SPAs are a good practice, so the experience is second-rate. I'm hoping new architectures like local-first prove where client-side applications are a good idea.

ghostdevv commented 5 days ago

Personally, I expected a +page.client.ts file that mirrors the format of +page.server.ts. It seems odd that you can create a server-side loader and a "universal" loader but not a client loader.

I think it makes sense to not have the +page.client.ts - if you're using SSR, only running on the client could cause hydration issues, and if you're CSR, it doesn't matter. However, I do agree that SvelteKit importing / evaluating +page.ts files on the "server" when SSR is disabled is annoying.

I've felt the pain of that myself plenty of times when building client-side apps, and I understand the frustration when considering past comments denouncing SPAs. However, we want SvelteKit to be the best tool for the job, even for SPAs!

there simply aren't viable CSR routers for Svelte other than SvelteKit

There are other options such as Routify, and numerous other declarative ones available. Routify v2 specifically should work just fine if you're using Svelte 3/4

braebo commented 5 days ago

Funny, @Rich-Harris spoke on this exact in a recent interview!

But, you know, amongst that, I at least am definitely thinking about what are the ideal integration points between the rendering framework and the data if the data exists in this sort of local first context.

bholmesdev commented 5 days ago

@ghostdevv Appreciate the thorough response here!

I do agree that SvelteKit importing / evaluating +page.ts files on the "server" when SSR is disabled is annoying.

Yeah that's all I'm getting at. I totally agree that +page.client.ts isn't a nice API. My understanding is that +page.ts must be executed at build-time to read the export const ssr flag, specifically for dynamic values like export const ssr = import.meta.env.DEV. My suggestion of +page.client.ts was to find some way to avoid server-side evaluation of these files entirely.

Also have a suggestion from maintaining Astro: it may help to remove dynamic value support for export const ssr so exports can be traced with an export crawler, without executing the file to determine the value. This was our approach for our prerender flag.

And I appreciate those links to community routers! I just prefer to use solutions that are first-party maintained and have a diverse contributor graph. I hadn't noticed the latest Routify v3 release though. May be worth a second look! In a perfect world, I'd use a SPA router within Astro ;)

benmccann commented 4 days ago

Thanks for pointing this out guys. I think it's an important issue, so added it to the SvelteKit 3 milestone to make sure we take a look at what can be done better here. I've heard at least a few options:

This is important as many people build SPAs in order to support things like capacitor and tauri and want to be able to directly reach for browser-only APIs without having to add if (browser) checks since they know their app will never have to run on a server