Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a straightforward way to export error pages with adapter-static #1209

Closed
intrikate opened this issue Apr 24, 2021 · 16 comments
Closed

Add a straightforward way to export error pages with adapter-static #1209

intrikate opened this issue Apr 24, 2021 · 16 comments

Comments

@intrikate
Copy link

intrikate commented Apr 24, 2021

Is your feature request related to a problem? Please describe.
There doesn’t seem to be a proper way to export a 404 for static sites. In Sapper, adding /404 as a second entry point would export an additional /404/index.html; in SvelteKit, there is currently no similar option.

Related to #1172, but with the specific goal of handling non-SPA exports.

Describe the solution you'd like
Assuming this has no value outside of static export, it probably should be a separate option for adapter-static. Error routes should not be required to explicitly exist and should be exempt from the default ‘fail on error’ policy.

(A nice touch would be the ability to trigger specific errors from here, e.g. by passing error codes for prerendering. This way, websites that need more than a simple 404 (e.g. sites operating within larger systems may also want 502 and 504) could be catered for. I wonder, however, if this is common enough to mind.)

Describe alternatives you've considered
In theory, offering a way to force prerendering no matter the status code would allow simply adding separate entries in the config, effectively replicating Sapper’s behaviour. This would be undesirable, as I believe SvelteKit’s ‘all errors are build errors’ behaviour is excellent and should remain otherwise intact.

A workaround currently requires error routes to exist as normal pages. This works, but leads to some duplication between $error.svelte and 404.svelte, is not very portable (a 404.svelte that in fact returns 200 makes no sense with, say, adapter-node), and generally ticks all boxes to qualify as a hack.

How important is this feature to you?
A 404 page is important in static export, and relying on hacks in performing such a common task shouldn’t be necessary.

@Rich-Harris
Copy link
Member

The fallback option is designed for this

@intrikate
Copy link
Author

The fallback option appears to disable SSR altogether, exporting a single HTML document that I assume is an SPA entry point. What I’m trying to do is export all routes as usual, plus a /404. Where am I wrong?

@Rich-Harris
Copy link
Member

If you're using fallback, you (currently; could add a new option, maybe) also need to set prerender = true for individual pages.

@intrikate
Copy link
Author

Oh, right, thanks, I missed this one. An option would be nice indeed.

However, the resulting fallback doesn’t render a 404 using $error.svelte, it simply renders a blank page with spa: true, which ultimately solves a different problem.

What am I still missing?

@lukeed
Copy link
Member

lukeed commented Jul 18, 2021

What I'm doing now is src/routes/404.svelte -- which builds to /build/404/index.html (currently using static adapter).

So then I add a postbuild npm script to move that file to the correct location:

{
  // ...
  "scripts": {
    "build": "svelte-kit build",
    "postbuild": "mv build/404/index.html build/404.html"
  }
}

@vwkd
Copy link

vwkd commented Aug 5, 2021

I've run into the same issue. A static error page is missing. I'd like to see an option to export __error.svelte into mypath/myname.html. Maybe something like

// svelte.config.js
import adapter from '@sveltejs/adapter-static';

export default {
	kit: {
		adapter: adapter({
			error: 'mypath/myname.html'
		})
	}
};

This is needed for Cloudflare Pages to show an error page. By default, Cloudflare Pages redirects all invalid URLs back to the root, because it assumes SPA mode if there is no 404.html present (see ​Not Found behavior).

Currently, it's even impossible to export such 404.html manually, because a 404.svelte generates a 404/index.html instead of a 404.html.

@lukeed
Copy link
Member

lukeed commented Aug 5, 2021

@vwkd Right now, the best way is to build a routes/404.svelte file and the include a postbuild script that moves the built 404/index.html into the the build directory's root as 404.html

Before the recent output dir changes, this would look like:

{
  "scripts": {
    // ...
    "postbuild": "mv build/404/index.html build/404.html"
  }
}

cuibonobo added a commit to cuibonobo/cuibonobo.com that referenced this issue Aug 23, 2021
Svelte Kit doesn't offer a good way to generate single HTML pages like 404.html, so I have to generate a regular page and then move it. Solution suggested here: sveltejs/kit#1209 (comment)
@urbainy
Copy link

urbainy commented Nov 28, 2021

I am trying to understand the working concept of __error.svelte. When a user inputted a non-exist address in his browser and press enter. I believe this GET request is sent to the server. According to my back-end (non-NODE) program, it cannot find this non-exist page file for sure so it sent index.html in the build folder of SK back to the browser. But in my browser, the error page is shown instead of index.html. This is a reasonable result and what I want. Just I don't know how this happens. I don't know how and when the error page is involved.

ky28059 added a commit to united-computations/GunnHacks8.0 that referenced this issue Feb 8, 2022
This involves using a really dumb `postbuild` script to make sure the generated `404.html` is actually in the right place. See sveltejs/kit#1209 (comment).
@Rich-Harris
Copy link
Member

fallback and the new prerender.default: true option make it straightforward to render a fallback 404.html (or 200.html, in the SPA case) page, so I'll close this issue

@fvilches17
Copy link

Summary for future readers

You can have your cake and eat it too. 🍰 i.e. :

  • Use sveltekit adapter-static
  • Pre-render all your pages by default, hence better performance and SEO (Search Engine Optimization)
  • Fallback to an error page when navigating to a non-existent page or resource
  • The fallback works locally and even if the site is hosted via a static provider (e.g. Cloudflare, Azure Static Web Apps, etc.)

Step 1: create error page

Step 2: update sveltekit.config.js

import adapter from '@sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	preprocess: preprocess(),
	
	kit: {
		adapter: adapter({
			pages: 'build',
			assets: 'build',
			fallback: '404.html', <------- HERE
			precompress: false
		}),
		prerender: {
			default: true <------- HERE
		}
		
		// other configs...
	}

	// other configs...
};

export default config;

Step 3: let your static host service know how to handle not found resources. As an example, I'm using the Azure Static Websites config

// staticwebapp.config.json
{
    "responseOverrides": {
        "404": {
            "rewrite": "/404.html",
            "statusCode": 404
        }
    }
}

jeroenheijmans added a commit to jeroenheijmans/tierdom that referenced this issue Mar 3, 2023
See sveltejs/kit#1209

The "prerender" instruction on the `kit` config from that thread didn't
seem necessary, probably because my `+layout.server.ts` had that set up
already? Regardless, putting `prerender: { default: true }` wasn't liked
by vite in my current setup...
@jeroenheijmans
Copy link

jeroenheijmans commented Mar 3, 2023

A remix of the above comment (thanks for that! <3), because prerender: { default: true } wasn't liked by my setup that uses GitHub Pages + SvelteKit + Tailwind + Vite as it gave me:

error when starting dev server:
Error: Unexpected option config.kit.prerender.default

Below is the tweaked setup that did work for me. I have a full SSR app (well, I guess the 404.html page is a mini-SPA? 😛), relevant settings are for my GitHub Pages / Adapter-Static setup:

  • /src/routes/+error.svelte

    <script lang="ts">
      import { page } from '$app/stores';
    </script>
    
    <div class="mx-auto max-w-page">
      <h1 class="text-4xl font-bold mt-8">Woops!</h1>
      <p class="py-4 text-xl">
        {#if $page.status === 404}
          We checked everywhere, but the thing you wanted was NotFound. Sorry!
        {:else}
          An unexpected error occurred.
        {/if}
      </p>
    </div>
  • /src/routes/+layout.server.ts

    export const prerender = true;
  • /svelte.config.js

    import adapter from '@sveltejs/adapter-static';
    import { vitePreprocess } from '@sveltejs/kit/vite';
    
    /** @type {import('@sveltejs/kit').Config} */
    const config = {
      preprocess: vitePreprocess(),
    
      kit: {
        paths: {
          base: '', // GitHub Pages, but served on my own apex+www domain so served on the root
        },
        adapter: adapter({
          manifest: false,
          pages: 'docs',
          assets: 'docs',
          fallback: '404.html',
          precompress: false,
          strict: true,
        }),
      },
    };
    
    export default config;

Further notes:

  • /docs folder is where I deploy my GitHub pages from
  • base: '' must be different (see docs) if you don't serve on your custom domain
  • GitHub pages serves /404.html automatically
  • don't forget the empty /static/.nojekyll file to let GitHub know it's your own static app, not a Jekyll website

If I "Disable Javascript" in my browser, all normal pages work well with their SSR version. The 404.html page less so. But that's good enough for me for now.

@Raicuparta
Copy link

I ended up reaching the exact same conclusion as @jeroenheijmans, but it not working with javascript off is a bit of a bummer. It's the one thing missing here. Hoping there's a solution for this

@mikkelsvartveit
Copy link

I agree with @Raicuparta, this results in a blank page if JS is disabled in the browser. It seems like the fallback page is not prerendered along with the other routes, and instead relies on CSR.

@jeroenheijmans
Copy link

Note that this issue is closed because the original request was handled. If there's a feeling that something needs to happen to enable this kind of 404 fallback pages that work without javascript, we should consider asking the team/community in a fresh issue if that can be considered. (Caveat: I have not checked yet if there's such an issue already, closed or not. Perhaps it's been considered and rejected for the moment?)

Personally I find having a non-SSR-404-fallback just fine, at least for my current use cases.

@gusvd
Copy link

gusvd commented Oct 16, 2023

This solution doesn't work for Cloudflare Pages. The 404 page renders a blank page with a JS error:

[Error] Error: Not found: /nonexistant
(anonymous function) — start.d376b317.js:1:14523
ce — start.d376b317.js:1:15805
re — start.d376b317.js:1:7845
(anonymous function) — start.d376b317.js:3:1334
Lt — start.d376b317.js:3:1386
(anonymous function) — nonexistant:28

	handleError (app.e4577215.js:1:8331)
	Z (start.d376b317.js:1:16942)
	(anonymous function) (start.d376b317.js:1:14513)
	ce (start.d376b317.js:1:15805)
	re (start.d376b317.js:1:7845)
	(anonymous function) (start.d376b317.js:3:1334)
	Lt (start.d376b317.js:3:1386)
	(anonymous function) (nonexistant:28)
[Error] Failed to load resource: the server responded with a status of 404 () (__data.json, line 0)
[Error] Unhandled Promise Rejection: TypeError: Array.from requires an array-like object - not null or undefined

@gusvd
Copy link

gusvd commented Oct 17, 2023

It seems like the problem with the solution proposed by @jeroenheijmans is that /src/routes/+error.svelte cannot use any data coming from a +page.server.js or +layout.server.js. The generated fallback 404 page cannot find the data (__data.json) and gives the error:

[Error] Failed to load resource: the server responded with a status of 404 () (__data.json, line 0) -- https://domain.com/nonexistent/__data.json?x-sveltekit-invalidated=1

I also didn't like using fallback: '404.html' because it's supposed to be a fallback to dynamic pages. My site is SSG and I don't want to mess around with these settings.

My solution was to create the 404 file as:

/routes/404/+page.svelte

It can still use data from +page.server.js or +layout.server.js during build time and it saves the data in its own folder /404/__data.json.

Do not use {$page.status} or {$page.error.message} since it will return a 200 during build time. Simply hard code the 404 error you want to display on the page.

After build, you will get a 404.html in the root directory that will be used by Cloudflare Pages and (possibly since I haven't tested) Github Pages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants