Repository: sveltejs/kit
Pull request #6392 (2022-08-29 19:15:49)

Prerendering overhaul
# Migration

## General

`+server` pages can be prerendered, too now, by adding the `export const prerender = ..` option to them. By default they are not prerendered, but any `+server` page that is referenced from a prerendered page inherits the `prerender` option of that page unless explicitly set otherwise. This might lead to a situation where you now get a "Cannot prerender a +server file with POST, PATCH, PUT, or DELETE" error. To fix it, add `export const prerender = false;` to the `+server` file where it happens, or move the mutative methods in a different file.

## adapter-static

Instead of only checking the default for prerendering, the actual found routes are now checked for prerendering. If one of them is not prerenderable, an error is thrown unless you set the `fallback` option. If you get this error now, adjust your code so that 
- either the non-prerenderable routes are not crawled anymore
- or the non-prerenderable routes are made prerenderable
- or set the `fallback` option

# PR description

#6356.

* [x] differentiate between prerenderable and non-prerenderable endpoints
* [x] exclude routes with `prerender = true` from generated manifests
* [x] allow `'auto'` (prerender, but also include in manifests) as a value, alongside `true` (only prerender, exclude from manifests) and `false` (never prerender, include in manifests)
* [x] throw in adapter-static if any routes are `prerender = false` or `prerender = 'auto'` (or unknown, in the case of endpoints that are never fetched during prerendering)
* [x] docs 
* [x] idk, probably some tests and stuff

Not in scope for this PR: removing `config.kit.prerender.default` and using defaults from `+layout[.server].js` files. That can be added to #6197 once this is merged.

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [x] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [x] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. All changesets should be `patch` until SvelteKit 1.0


changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 3fb297e01890aacf6c93e03dec36968a41c2d08d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/sveltejs/kit/new/gh-6356?filename=.changeset/late-pugs-destroy.md&value=---%0A%22%40sveltejs%2Fkit%22%3A%20patch%0A---%0A%0APrerendering%20overhaul%0A)



Patch:
diff --git a/.changeset/cuddly-months-sip.md b/.changeset/cuddly-months-sip.md
new file mode 100644
index 000000000..ab4cba0be
--- /dev/null
+++ b/.changeset/cuddly-months-sip.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": patch
+---
+
+Prevent infinite reloads on `/` when root `+layout.server.js` exports `load`
diff --git a/.changeset/dull-cheetahs-clean.md b/.changeset/dull-cheetahs-clean.md
new file mode 100644
index 000000000..e1c8559ef
--- /dev/null
+++ b/.changeset/dull-cheetahs-clean.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[breaking] remove App.PrivateEnv and App.PublicEnv in favour of generated types
diff --git a/.changeset/lovely-otters-lie.md b/.changeset/lovely-otters-lie.md
new file mode 100644
index 000000000..7643a863b
--- /dev/null
+++ b/.changeset/lovely-otters-lie.md
@@ -0,0 +1,5 @@
+---
+'create-svelte': patch
+---
+
+Remove App.PrivateEnv and App.PublicEnv placeholders
diff --git a/.changeset/neat-pots-flash.md b/.changeset/neat-pots-flash.md
new file mode 100644
index 000000000..80fa9c864
--- /dev/null
+++ b/.changeset/neat-pots-flash.md
@@ -0,0 +1,6 @@
+---
+"create-svelte": patch
+"@sveltejs/kit": patch
+---
+
+[breaking] Replace `sveltekit:*` with valid HTML attributes like `data-sveltekit-*`
diff --git a/.changeset/popular-bulldogs-repair.md b/.changeset/popular-bulldogs-repair.md
new file mode 100644
index 000000000..c6f2b585b
--- /dev/null
+++ b/.changeset/popular-bulldogs-repair.md
@@ -0,0 +1,5 @@
+---
+'create-svelte': patch
+---
+
+Update to Vite 3.1.0-beta.1
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 01c0ca833..47b6e4835 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -169,6 +169,7 @@
     "cuddly-ads-kick",
     "cuddly-files-decide",
     "cuddly-knives-learn",
+    "cuddly-months-sip",
     "cuddly-pens-buy",
     "cuddly-ravens-carry",
     "curly-doors-type",
@@ -198,6 +199,7 @@
     "dry-cars-dance",
     "dry-jokes-explain",
     "dry-yaks-clean",
+    "dull-cheetahs-clean",
     "dull-ducks-report",
     "dull-emus-tap",
     "dull-lobsters-act",
@@ -544,6 +546,7 @@
     "lovely-foxes-visit",
     "lovely-hairs-applaud",
     "lovely-mice-search",
+    "lovely-otters-lie",
     "lovely-seahorses-sell",
     "lucky-bottles-kick",
     "lucky-glasses-sell",
@@ -622,6 +625,7 @@
     "neat-monkeys-dream",
     "neat-olives-hope",
     "neat-points-exercise",
+    "neat-pots-flash",
     "neat-walls-buy",
     "neat-worms-tell",
     "nervous-items-kiss",
@@ -731,6 +735,7 @@
     "poor-terms-explain",
     "poor-years-hear",
     "popular-ads-rhyme",
+    "popular-bulldogs-repair",
     "popular-hounds-love",
     "popular-kids-run",
     "popular-masks-cheat",
@@ -1162,6 +1167,7 @@
     "two-tigers-cry",
     "unlucky-cameras-appear",
     "unlucky-dodos-greet",
+    "unlucky-elephants-appear",
     "unlucky-mugs-dress",
     "unlucky-oranges-exercise",
     "unlucky-otters-tickle",
diff --git a/.changeset/proud-laws-exist.md b/.changeset/proud-laws-exist.md
new file mode 100644
index 000000000..9ac3739fb
--- /dev/null
+++ b/.changeset/proud-laws-exist.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-static': patch
+---
+
+[breaking] require all routes to be prerenderable when not using fallback option
diff --git a/.changeset/spicy-taxis-wave.md b/.changeset/spicy-taxis-wave.md
new file mode 100644
index 000000000..ead403611
--- /dev/null
+++ b/.changeset/spicy-taxis-wave.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[breaking] add `prerender = 'auto'` option, and extend `prerender` option to endpoints
diff --git a/.changeset/unlucky-elephants-appear.md b/.changeset/unlucky-elephants-appear.md
new file mode 100644
index 000000000..4a51f1b04
--- /dev/null
+++ b/.changeset/unlucky-elephants-appear.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Include type descriptions for ambient declarations
diff --git a/documentation/docs/02-web-standards.md b/documentation/docs/02-web-standards.md
index 4cde6b099..8c5c09a18 100644
--- a/documentation/docs/02-web-standards.md
+++ b/documentation/docs/02-web-standards.md
@@ -10,7 +10,7 @@ In particular, you'll get comfortable with the following:
 
 ### Fetch APIs
 
-SvelteKit uses [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) for getting data from the network. It's available in [hooks](/docs/hooks) and [endpoints](/docs/routing#endpoints) as well as in the browser.
+SvelteKit uses [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) for getting data from the network. It's available in [hooks](/docs/hooks) and [server routes](/docs/routing#server) as well as in the browser.
 
 > A special version of `fetch` is available in [`load`](/docs/load) functions for invoking endpoints directly during server-side rendering, without making an HTTP call, while preserving credentials. (To make credentialled fetches in server-side code outside `load`, you must explicitly pass `cookie` and/or `authorization` headers.) It also allows you to make relative requests, whereas server-side `fetch` normally requires a fully qualified URL.
 
@@ -18,7 +18,7 @@ Besides `fetch` itself, the [Fetch API](https://developer.mozilla.org/en-US/docs
 
 #### Request
 
-An instance of [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) is accessible in [hooks](/docs/hooks) and [endpoints](/docs/routing#endpoints) as `event.request`. It contains useful methods like `request.json()` and `request.formData()` for e.g. getting data that was posted to an endpoint.
+An instance of [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) is accessible in [hooks](/docs/hooks) and [server routes](/docs/routing#server) as `event.request`. It contains useful methods like `request.json()` and `request.formData()` for e.g. getting data that was posted to an endpoint.
 
 #### Response
 
@@ -51,7 +51,7 @@ Most of the time, your endpoints will return complete data, as in the `userAgent
 
 ### URL APIs
 
-URLs are represented by the [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) interface, which includes useful properties like `origin` and `pathname` (and, in the browser, `hash`). This interface shows up in various places — `event.url` in [hooks](/docs/hooks) and [endpoints](/docs/routing#endpoints), [`$page.url`](/docs/modules#$app-stores) in [pages](/docs/routing#pages), `from` and `to` in [`beforeNavigate` and `afterNavigate`](/docs/modules#$app-navigation) and so on.
+URLs are represented by the [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) interface, which includes useful properties like `origin` and `pathname` (and, in the browser, `hash`). This interface shows up in various places — `event.url` in [hooks](/docs/hooks) and [server routes](/docs/routing#server), [`$page.url`](/docs/modules#$app-stores) in [pages](/docs/routing#pages), `from` and `to` in [`beforeNavigate` and `afterNavigate`](/docs/modules#$app-navigation) and so on.
 
 #### URLSearchParams
 
diff --git a/documentation/docs/05-load.md b/documentation/docs/05-load.md
index 2eebfb9d3..eb83c9111 100644
--- a/documentation/docs/05-load.md
+++ b/documentation/docs/05-load.md
@@ -332,7 +332,7 @@ To redirect users, use the `redirect` helper from `@sveltejs/kit` to specify the
 -import { error } from '@sveltejs/kit';
 +import { error, redirect } from '@sveltejs/kit';
 
-/** @type {import('./$types').LayoutLoad} */
+/** @type {import('./$types').LayoutServerLoad} */
 export function load({ locals }) {
 	if (!locals.user) {
 -		throw error(401, 'not logged in');
diff --git a/documentation/docs/09-a-options.md b/documentation/docs/09-a-options.md
index ac5f6206a..c5184f779 100644
--- a/documentation/docs/09-a-options.md
+++ b/documentation/docs/09-a-options.md
@@ -2,16 +2,16 @@
 title: Anchor options
 ---
 
-### sveltekit:prefetch
+### data-sveltekit-prefetch
 
 SvelteKit uses code splitting to break your app into small chunks (one per route), ensuring fast startup times.
 
 For _dynamic_ routes, such as our `src/routes/blog/[slug]/+page.svelte` example, that's not enough. In order to render the blog post, we need to fetch the data for it, and we can't do that until we know what `slug` is. In the worst case, that could cause lag as the browser waits for the data to come back from the server.
 
-We can mitigate that by _prefetching_ the data. Adding a `sveltekit:prefetch` attribute to a link...
+We can mitigate that by _prefetching_ the data. Adding a `data-sveltekit-prefetch` attribute to a link...
 
 ```html
-<a sveltekit:prefetch href="blog/what-is-sveltekit">What is SvelteKit?</a>
+<a data-sveltekit-prefetch href="blog/what-is-sveltekit">What is SvelteKit?</a>
 ```
 
 ...will cause SvelteKit to run the page's `load` function as soon as the user hovers over the link (on a desktop) or touches it (on mobile), rather than waiting for the `click` event to trigger navigation. Typically, this buys us an extra couple of hundred milliseconds, which is the difference between a user interface that feels laggy, and one that feels snappy.
@@ -20,28 +20,28 @@ Note that prefetching will not work if the [`router`](/docs/page-options#router)
 
 You can also programmatically invoke `prefetch` from `$app/navigation`.
 
-### sveltekit:reload
+### data-sveltekit-reload
 
 By default, the SvelteKit runtime intercepts clicks on `<a>` elements and bypasses the normal browser navigation for relative (same-origin) URLs that match one of your page routes. We sometimes need to tell SvelteKit that certain links need to be handled by normal browser navigation. Examples of this might be linking to another page on your domain that's not part of your SvelteKit app or linking to an endpoint.
 
-Adding a `sveltekit:reload` attribute to a link...
+Adding a `data-sveltekit-reload` attribute to a link...
 
 ```html
-<a sveltekit:reload href="path">Path</a>
+<a data-sveltekit-reload href="path">Path</a>
 ```
 
 ...will cause browser to navigate via a full page reload when the link is clicked.
 
 Links with a `rel="external"` attribute will receive the same treatment. In addition, they will be ignored during [prerendering](https://kit.svelte.dev/docs/page-options#prerender).
 
-### sveltekit:noscroll
+### data-sveltekit-noscroll
 
 When navigating to internal links, SvelteKit mirrors the browser's default navigation behaviour: it will change the scroll position to 0,0 so that the user is at the very top left of the page (unless the link includes a `#hash`, in which case it will scroll to the element with a matching ID).
 
-In certain cases, you may wish to disable this behaviour. Adding a `sveltekit:noscroll` attribute to a link...
+In certain cases, you may wish to disable this behaviour. Adding a `data-sveltekit-noscroll` attribute to a link...
 
 ```html
-<a href="path" sveltekit:noscroll>Path</a>
+<a href="path" data-sveltekit-noscroll>Path</a>
 ```
 
 ...will prevent scrolling after the link is clicked.
diff --git a/documentation/docs/11-adapters.md b/documentation/docs/11-adapters.md
index 8de06281d..2195b3354 100644
--- a/documentation/docs/11-adapters.md
+++ b/documentation/docs/11-adapters.md
@@ -58,7 +58,7 @@ You can also use `adapter-static` to generate single-page apps (SPAs) by specify
 
 #### Platform-specific context
 
-Some adapters may have access to additional information about the request. For example, Cloudflare Workers can access an `env` object containing KV namespaces etc. This can be passed to the `RequestEvent` used in [hooks](/docs/hooks) and [endpoints](/docs/routing#endpoints) as the `platform` property — consult each adapter's documentation to learn more.
+Some adapters may have access to additional information about the request. For example, Cloudflare Workers can access an `env` object containing KV namespaces etc. This can be passed to the `RequestEvent` used in [hooks](/docs/hooks) and [server routes](/docs/routing#server) as the `platform` property — consult each adapter's documentation to learn more.
 
 ### Community adapters
 
diff --git a/documentation/docs/12-page-options.md b/documentation/docs/12-page-options.md
index a00448f10..e0ed3ef0c 100644
--- a/documentation/docs/12-page-options.md
+++ b/documentation/docs/12-page-options.md
@@ -32,22 +32,29 @@ export const hydrate = false;
 
 ### prerender
 
-It's likely that at least some pages of your app can be represented as a simple HTML file generated at build time. These pages can be [_prerendered_](/docs/appendix#prerendering).
+It's likely that at least some routes of your app can be represented as a simple HTML file generated at build time. These routes can be [_prerendered_](/docs/appendix#prerendering).
 
-Prerendering happens automatically for any page with the `prerender` annotation:
+Prerendering happens automatically for any `+page` or `+server` file with the `prerender` annotation:
 
 ```js
-/// file: +page.js/+page.server.js
+/// file: +page.js/+page.server.js/+server.js
 export const prerender = true;
 ```
 
 Alternatively, you can set [`config.kit.prerender.default`](/docs/configuration#prerender) to `true` and prerender everything except pages that are explicitly marked as _not_ prerenderable:
 
 ```js
-/// file: +page.js/+page.server.js
+/// file: +page.js/+page.server.js/+server.js
 export const prerender = false;
 ```
 
+Routes with `prerender = true` will be excluded from manifests used for dynamic SSR, making your server (or serverless/edge functions) smaller. In some cases you might want to prerender a route but also include it in the manifest (for example, you want to prerender your most recent/popular content but server-render the long tail) — for these cases, there's a third option, 'auto':
+
+```js
+/// file: +page.js/+page.server.js/+server.js
+export const prerender = 'auto';
+```
+
 > If your entire app is suitable for prerendering, you can use [`adapter-static`](https://github.com/sveltejs/kit/tree/master/packages/adapter-static), which will output files suitable for use with any static webserver.
 
 The prerenderer will start at the root of your app and generate HTML for any prerenderable pages it finds. Each page is scanned for `<a>` elements that point to other pages that are candidates for prerendering — because of this, you generally don't need to specify which pages should be accessed. If you _do_ need to specify which pages should be accessed by the prerenderer, you can do so with the `entries` option in the [prerender configuration](/docs/configuration#prerender).
diff --git a/documentation/docs/80-migrating.md b/documentation/docs/80-migrating.md
index 1ef080470..bc3449408 100644
--- a/documentation/docs/80-migrating.md
+++ b/documentation/docs/80-migrating.md
@@ -133,12 +133,12 @@ This caused problems and is no longer the case in SvelteKit. Instead, relative U
 
 #### &lt;a&gt; attributes
 
-- `sapper:prefetch` is now `sveltekit:prefetch`
-- `sapper:noscroll` is now `sveltekit:noscroll`
+- `sapper:prefetch` is now `data-sveltekit-prefetch`
+- `sapper:noscroll` is now `data-sveltekit-noscroll`
 
 ### Endpoints
 
-In Sapper, 'server routes' — now referred to as [endpoints](/docs/routing#endpoints) — received the `req` and `res` objects exposed by Node's `http` module (or the augmented versions provided by frameworks like Polka and Express).
+In Sapper, [server routes](/docs/routing#server) received the `req` and `res` objects exposed by Node's `http` module (or the augmented versions provided by frameworks like Polka and Express).
 
 SvelteKit is designed to be agnostic as to where the app is running — it could be running on a Node server, but could equally be running on a serverless platform or in a Cloudflare Worker. For that reason, you no longer interact directly with `req` and `res`. Your endpoints will need to be updated to match the new signature.
 
diff --git a/documentation/faq/80-integrations.md b/documentation/faq/80-integrations.md
index 10991dad5..1d8931194 100644
--- a/documentation/faq/80-integrations.md
+++ b/documentation/faq/80-integrations.md
@@ -6,7 +6,7 @@ Make sure you've read the [documentation section on integrations](/docs/addition
 
 ### How do I setup a database?
 
-Put the code to query your database in [endpoints](/docs/routing#endpoints) - don't query the database in .svelte files. You can create a `db.js` or similar that sets up a connection immediately and makes the client accessible throughout the app as a singleton. You can execute any one-time setup code in `hooks.js` and import your database helpers into any endpoint that needs them.
+Put the code to query your database in a [server route](/docs/routing#server) - don't query the database in .svelte files. You can create a `db.js` or similar that sets up a connection immediately and makes the client accessible throughout the app as a singleton. You can execute any one-time setup code in `hooks.js` and import your database helpers into any endpoint that needs them.
 
 ### How do I use middleware?
 
diff --git a/packages/adapter-static/index.js b/packages/adapter-static/index.js
index 9068a0730..8d6deae6f 100644
--- a/packages/adapter-static/index.js
+++ b/packages/adapter-static/index.js
@@ -1,3 +1,4 @@
+import path from 'path';
 import { platforms } from './platforms.js';
 
 /** @type {import('.').default} */
@@ -6,10 +7,33 @@ export default function (options) {
 		name: '@sveltejs/adapter-static',
 
 		async adapt(builder) {
-			if (!options?.fallback && !builder.config.kit.prerender.default) {
-				throw Error(
-					'adapter-static requires `config.kit.prerender.default` to be `true` unless you set the `fallback: true` option to create a single-page app. See https://github.com/sveltejs/kit/tree/master/packages/adapter-static#spa-mode for more information'
-				);
+			if (!options?.fallback) {
+				/** @type {string[]} */
+				const dynamic_routes = [];
+
+				// this is a bit of a hack — it allows us to know whether there are dynamic
+				// (i.e. prerender = false/'auto') routes without having dedicated API
+				// surface area for it
+				builder.createEntries((route) => {
+					dynamic_routes.push(route.id);
+
+					return {
+						id: '',
+						filter: () => false,
+						complete: () => {}
+					};
+				});
+
+				if (dynamic_routes.length > 0) {
+					const prefix = path.relative('.', builder.config.kit.files.routes);
+					builder.log.error(
+						`@sveltejs/adapter-static: cannot have dynamic routes unless using the 'fallback' option. See https://github.com/sveltejs/kit/tree/master/packages/adapter-static#spa-mode for more information`
+					);
+					builder.log.error(
+						dynamic_routes.map((id) => `  - ${path.posix.join(prefix, id)}`).join('\n')
+					);
+					throw new Error('Encountered dynamic routes');
+				}
 			}
 
 			const platform = platforms.find((platform) => platform.test());
diff --git a/packages/create-svelte/CHANGELOG.md b/packages/create-svelte/CHANGELOG.md
index 54cabbc40..0326b3754 100644
--- a/packages/create-svelte/CHANGELOG.md
+++ b/packages/create-svelte/CHANGELOG.md
@@ -1,5 +1,23 @@
 # create-svelte
 
+## 2.0.0-next.167
+
+### Patch Changes
+
+- [breaking] Replace `sveltekit:*` with valid HTML attributes like `data-sveltekit-*` ([#6170](https://github.com/sveltejs/kit/pull/6170))
+
+## 2.0.0-next.166
+
+### Patch Changes
+
+- Remove App.PrivateEnv and App.PublicEnv placeholders ([#6413](https://github.com/sveltejs/kit/pull/6413))
+
+## 2.0.0-next.165
+
+### Patch Changes
+
+- Update to Vite 3.1.0-beta.1 ([#6407](https://github.com/sveltejs/kit/pull/6407))
+
 ## 2.0.0-next.164
 
 ### Patch Changes
diff --git a/packages/create-svelte/package.json b/packages/create-svelte/package.json
index c8dda9b86..319ea0f25 100644
--- a/packages/create-svelte/package.json
+++ b/packages/create-svelte/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "create-svelte",
-	"version": "2.0.0-next.164",
+	"version": "2.0.0-next.167",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/create-svelte/templates/default/src/app.d.ts b/packages/create-svelte/templates/default/src/app.d.ts
index a4416caba..d7506f89a 100644
--- a/packages/create-svelte/templates/default/src/app.d.ts
+++ b/packages/create-svelte/templates/default/src/app.d.ts
@@ -9,8 +9,4 @@ declare namespace App {
 	// interface PageData {}
 
 	// interface Platform {}
-
-	// interface PrivateEnv {}
-
-	// interface PublicEnv {}
 }
diff --git a/packages/create-svelte/templates/default/src/lib/header/Header.svelte b/packages/create-svelte/templates/default/src/lib/header/Header.svelte
index 9d3120f3e..a048e0ecc 100644
--- a/packages/create-svelte/templates/default/src/lib/header/Header.svelte
+++ b/packages/create-svelte/templates/default/src/lib/header/Header.svelte
@@ -15,12 +15,14 @@
 			<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
 		</svg>
 		<ul>
-			<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
+			<li class:active={$page.url.pathname === '/'}>
+				<a data-sveltekit-prefetch href="/">Home</a>
+			</li>
 			<li class:active={$page.url.pathname === '/about'}>
-				<a sveltekit:prefetch href="/about">About</a>
+				<a data-sveltekit-prefetch href="/about">About</a>
 			</li>
 			<li class:active={$page.url.pathname === '/todos'}>
-				<a sveltekit:prefetch href="/todos">Todos</a>
+				<a data-sveltekit-prefetch href="/todos">Todos</a>
 			</li>
 		</ul>
 		<svg viewBox="0 0 2 3" aria-hidden="true">
diff --git a/packages/create-svelte/templates/libskeleton/src/app.d.ts b/packages/create-svelte/templates/libskeleton/src/app.d.ts
index 637dfc2d0..3a4446871 100644
--- a/packages/create-svelte/templates/libskeleton/src/app.d.ts
+++ b/packages/create-svelte/templates/libskeleton/src/app.d.ts
@@ -7,6 +7,4 @@ declare namespace App {
 	// interface Locals {}
 	// interface PageData {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/create-svelte/templates/skeleton/src/app.d.ts b/packages/create-svelte/templates/skeleton/src/app.d.ts
index 7ef2c828b..14ac5d1b5 100644
--- a/packages/create-svelte/templates/skeleton/src/app.d.ts
+++ b/packages/create-svelte/templates/skeleton/src/app.d.ts
@@ -5,6 +5,4 @@ declare namespace App {
 	// interface Locals {}
 	// interface PageData {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index a94320c28..d54582e49 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,29 @@
 # @sveltejs/kit
 
+## 1.0.0-next.454
+
+### Patch Changes
+
+- [breaking] Replace `sveltekit:*` with valid HTML attributes like `data-sveltekit-*` ([#6170](https://github.com/sveltejs/kit/pull/6170))
+
+## 1.0.0-next.453
+
+### Patch Changes
+
+- Include type descriptions for ambient declarations ([#6416](https://github.com/sveltejs/kit/pull/6416))
+
+## 1.0.0-next.452
+
+### Patch Changes
+
+- [breaking] remove App.PrivateEnv and App.PublicEnv in favour of generated types ([#6413](https://github.com/sveltejs/kit/pull/6413))
+
+## 1.0.0-next.451
+
+### Patch Changes
+
+- Prevent infinite reloads on `/` when root `+layout.server.js` exports `load` ([#6405](https://github.com/sveltejs/kit/pull/6405))
+
 ## 1.0.0-next.450
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 8da082c48..9cfaa74a3 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.450",
+	"version": "1.0.0-next.454",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
@@ -53,7 +53,8 @@
 		"!src/core/**/fixtures",
 		"!src/core/**/test",
 		"types",
-		"svelte-kit.js"
+		"svelte-kit.js",
+		"scripts/special-types"
 	],
 	"scripts": {
 		"build": "npm run types",
diff --git a/packages/kit/scripts/special-types/$env+dynamic+private.md b/packages/kit/scripts/special-types/$env+dynamic+private.md
new file mode 100644
index 000000000..85dc7c70a
--- /dev/null
+++ b/packages/kit/scripts/special-types/$env+dynamic+private.md
@@ -0,0 +1,8 @@
+This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) (or running [`vite preview`](https://kit.svelte.dev/docs/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#kit-env-publicprefix).
+
+This module cannot be imported into client-side code.
+
+```ts
+import { env } from '$env/dynamic/private';
+console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
+```
diff --git a/packages/kit/scripts/special-types/$env+dynamic+public.md b/packages/kit/scripts/special-types/$env+dynamic+public.md
new file mode 100644
index 000000000..2c2ccdce9
--- /dev/null
+++ b/packages/kit/scripts/special-types/$env+dynamic+public.md
@@ -0,0 +1,8 @@
+Similar to [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), but only includes variables that begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#kit-env-publicprefix) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
+
+Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests — when possible, use `$env/static/public` instead.
+
+```ts
+import { env } from '$env/dynamic/public';
+console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
+```
diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js
index e434d947f..b79fb68d7 100644
--- a/packages/kit/src/core/adapt/builder.js
+++ b/packages/kit/src/core/adapt/builder.js
@@ -5,57 +5,21 @@ import { pipeline } from 'stream';
 import { promisify } from 'util';
 import { copy, rimraf, mkdirp } from '../../utils/filesystem.js';
 import { generate_manifest } from '../generate_manifest/index.js';
-import { get_path } from '../../utils/routing.js';
+
+const pipe = promisify(pipeline);
 
 /**
  * Creates the Builder which is passed to adapters for building the application.
  * @param {{
  *   config: import('types').ValidatedConfig;
  *   build_data: import('types').BuildData;
+ *   routes: import('types').RouteData[];
  *   prerendered: import('types').Prerendered;
  *   log: import('types').Logger;
  * }} opts
  * @returns {import('types').Builder}
  */
-export function create_builder({ config, build_data, prerendered, log }) {
-	/** @type {Set<string>} */
-	const prerendered_paths = new Set(prerendered.paths);
-
-	/** @param {import('types').RouteData} route */
-	// TODO routes should come pre-filtered
-	function not_prerendered(route) {
-		const path = route.page && get_path(route.id);
-		if (path) {
-			return !prerendered_paths.has(path) && !prerendered_paths.has(path + '/');
-		}
-
-		return true;
-	}
-
-	const pipe = promisify(pipeline);
-
-	/**
-	 * @param {string} file
-	 * @param {'gz' | 'br'} format
-	 */
-	async function compress_file(file, format = 'gz') {
-		const compress =
-			format == 'br'
-				? zlib.createBrotliCompress({
-						params: {
-							[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT,
-							[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY,
-							[zlib.constants.BROTLI_PARAM_SIZE_HINT]: statSync(file).size
-						}
-				  })
-				: zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION });
-
-		const source = createReadStream(file);
-		const destination = createWriteStream(`${file}.${format}`);
-
-		await pipe(source, compress, destination);
-	}
-
+export function create_builder({ config, build_data, routes, prerendered, log }) {
 	return {
 		log,
 		rimraf,
@@ -66,8 +30,6 @@ export function create_builder({ config, build_data, prerendered, log }) {
 		prerendered,
 
 		async createEntries(fn) {
-			const { routes } = build_data.manifest_data;
-
 			/** @type {import('types').RouteDefinition[]} */
 			const facades = routes.map((route) => {
 				const methods = new Set();
@@ -113,7 +75,7 @@ export function create_builder({ config, build_data, prerendered, log }) {
 					}
 				}
 
-				const filtered = new Set(group.filter(not_prerendered));
+				const filtered = new Set(group);
 
 				// heuristic: if /foo/[bar] is included, /foo/[bar].json should
 				// also be included, since the page likely needs the endpoint
@@ -146,7 +108,7 @@ export function create_builder({ config, build_data, prerendered, log }) {
 			return generate_manifest({
 				build_data,
 				relative_path: relativePath,
-				routes: build_data.manifest_data.routes.filter(not_prerendered),
+				routes,
 				format
 			});
 		},
@@ -221,3 +183,25 @@ export function create_builder({ config, build_data, prerendered, log }) {
 		}
 	};
 }
+
+/**
+ * @param {string} file
+ * @param {'gz' | 'br'} format
+ */
+async function compress_file(file, format = 'gz') {
+	const compress =
+		format == 'br'
+			? zlib.createBrotliCompress({
+					params: {
+						[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT,
+						[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY,
+						[zlib.constants.BROTLI_PARAM_SIZE_HINT]: statSync(file).size
+					}
+			  })
+			: zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION });
+
+	const source = createReadStream(file);
+	const destination = createWriteStream(`${file}.${format}`);
+
+	await pipe(source, compress, destination);
+}
diff --git a/packages/kit/src/core/adapt/builder.spec.js b/packages/kit/src/core/adapt/builder.spec.js
index 2f2c82f4a..af35cc19f 100644
--- a/packages/kit/src/core/adapt/builder.spec.js
+++ b/packages/kit/src/core/adapt/builder.spec.js
@@ -29,6 +29,7 @@ test('copy files', () => {
 		config: /** @type {import('types').ValidatedConfig} */ (mocked),
 		// @ts-expect-error
 		build_data: {},
+		routes: [],
 		// @ts-expect-error
 		prerendered: {
 			paths: []
diff --git a/packages/kit/src/core/adapt/index.js b/packages/kit/src/core/adapt/index.js
index 25d02039f..26feb5674 100644
--- a/packages/kit/src/core/adapt/index.js
+++ b/packages/kit/src/core/adapt/index.js
@@ -5,14 +5,26 @@ import { create_builder } from './builder.js';
  * @param {import('types').ValidatedConfig} config
  * @param {import('types').BuildData} build_data
  * @param {import('types').Prerendered} prerendered
+ * @param {import('types').PrerenderMap} prerender_map
  * @param {{ log: import('types').Logger }} opts
  */
-export async function adapt(config, build_data, prerendered, { log }) {
+export async function adapt(config, build_data, prerendered, prerender_map, { log }) {
 	const { name, adapt } = config.kit.adapter;
 
 	console.log(colors.bold().cyan(`\n> Using ${name}`));
 
-	const builder = create_builder({ config, build_data, prerendered, log });
+	const builder = create_builder({
+		config,
+		build_data,
+		routes: build_data.manifest_data.routes.filter((route) => {
+			if (!route.page && !route.endpoint) return false;
+
+			const prerender = prerender_map.get(route.id);
+			return prerender === false || prerender === undefined || prerender === 'auto';
+		}),
+		prerendered,
+		log
+	});
 	await adapt(builder);
 
 	log.success('done');
diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js
index 789348a62..00291a4d0 100644
--- a/packages/kit/src/core/env.js
+++ b/packages/kit/src/core/env.js
@@ -34,7 +34,7 @@ export function create_dynamic_module(type) {
  * @param {Record<string, string>} env
  * @returns {string}
  */
-export function create_types(id, env) {
+export function create_static_types(id, env) {
 	const declarations = Object.keys(env)
 		.filter((k) => valid_identifier.test(k))
 		.map((k) => `\texport const ${k}: string;`)
@@ -43,6 +43,21 @@ export function create_types(id, env) {
 	return `declare module '${id}' {\n${declarations}\n}`;
 }
 
+/**
+ * @param {string} id
+ * @param {Record<string, string>} env
+ * @returns {string}
+ */
+export function create_dynamic_types(id, env) {
+	const properties = Object.keys(env)
+		.filter((k) => valid_identifier.test(k))
+		.map((k) => `\t\t${k}: string;`);
+
+	properties.push(`\t\t[key: string]: string | undefined;`);
+
+	return `declare module '${id}' {\n\texport const env: {\n${properties.join('\n')}\n\t}\n}`;
+}
+
 export const reserved = new Set([
 	'do',
 	'if',
diff --git a/packages/kit/src/core/prerender/fixtures/basic-href/input.html b/packages/kit/src/core/prerender/fixtures/basic-href/input.html
index ec2f5dc57..a294e070d 100644
--- a/packages/kit/src/core/prerender/fixtures/basic-href/input.html
+++ b/packages/kit/src/core/prerender/fixtures/basic-href/input.html
@@ -7,8 +7,8 @@
 	<body>
 		<a href="https://external.com">https://external.com</a>
 		<a href="/wheee">/wheee</a>
-		<a sveltekit:prefetch href="/wheee2">/wheee</a>
-		<a href="/wheee3" sveltekit:prefetch>/wheee</a>
+		<a data-sveltekit-prefetch href="/wheee2">/wheee</a>
+		<a href="/wheee3" data-sveltekit-prefetch>/wheee</a>
 		<a href="/wheee4">/wheee</a>
 	</body>
 </html>
diff --git a/packages/kit/src/core/prerender/prerender.js b/packages/kit/src/core/prerender/prerender.js
index 5c89fd3e8..2cdf7b89d 100644
--- a/packages/kit/src/core/prerender/prerender.js
+++ b/packages/kit/src/core/prerender/prerender.js
@@ -9,15 +9,14 @@ import { crawl } from './crawl.js';
 import { escape_html_attr } from '../../utils/escape.js';
 import { logger } from '../utils.js';
 import { load_config } from '../config/index.js';
-import { compact } from '../../utils/array.js';
-import { get_path } from '../../utils/routing.js';
+import { affects_path } from '../../utils/routing.js';
 
 /**
  * @typedef {import('types').PrerenderErrorHandler} PrerenderErrorHandler
  * @typedef {import('types').Logger} Logger
  */
 
-const [, , client_out_dir, results_path, manifest_path, verbose, env] = process.argv;
+const [, , client_out_dir, results_path, verbose, env] = process.argv;
 
 prerender();
 
@@ -58,12 +57,15 @@ const OK = 2;
 const REDIRECT = 3;
 
 /**
- * @param {import('types').Prerendered} prerendered
+ * @param {{
+ *   prerendered: import('types').Prerendered;
+ *   prerender_map: import('types').PrerenderMap;
+ * }} data
  */
-const output_and_exit = (prerendered) => {
+const output_and_exit = (data) => {
 	writeFileSync(
 		results_path,
-		JSON.stringify(prerendered, (_key, value) =>
+		JSON.stringify(data, (_key, value) =>
 			value instanceof Map ? Array.from(value.entries()) : value
 		)
 	);
@@ -79,11 +81,14 @@ export async function prerender() {
 		paths: []
 	};
 
+	/** @type {import('types').PrerenderMap} */
+	const prerender_map = new Map();
+
 	/** @type {import('types').ValidatedKitConfig} */
 	const config = (await load_config()).kit;
 
 	if (!config.prerender.enabled) {
-		output_and_exit(prerendered);
+		output_and_exit({ prerendered, prerender_map });
 		return;
 	}
 
@@ -231,6 +236,16 @@ export async function prerender() {
 			const encoded_dependency_path = new URL(dependency_path, 'http://localhost').pathname;
 			const decoded_dependency_path = decodeURI(encoded_dependency_path);
 
+			const prerender = result.response.headers.get('x-sveltekit-prerender');
+
+			if (prerender) {
+				const route_id = /** @type {string} */ (result.response.headers.get('x-sveltekit-routeid'));
+				const existing_value = prerender_map.get(route_id);
+				if (existing_value !== 'auto') {
+					prerender_map.set(route_id, prerender === 'true' ? true : 'auto');
+				}
+			}
+
 			const body = result.body ?? new Uint8Array(await result.response.arrayBuffer());
 			save(
 				'dependencies',
@@ -340,25 +355,63 @@ export async function prerender() {
 		}
 	}
 
-	if (config.prerender.enabled) {
-		for (const entry of config.prerender.entries) {
-			if (entry === '*') {
-				/** @type {import('types').SSRManifest} */
-				const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
-				const { routes } = manifest._;
-				const entries = compact(routes.map((route) => route.page && get_path(route.id)));
+	for (const route of manifest._.routes) {
+		try {
+			if (route.endpoint) {
+				const mod = await route.endpoint();
+				if (mod.prerender !== undefined) {
+					if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
+						throw new Error(
+							`Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})`
+						);
+					}
 
-				for (const entry of entries) {
-					enqueue(null, config.paths.base + entry); // TODO can we pre-normalize these?
+					prerender_map.set(route.id, mod.prerender);
 				}
-			} else {
-				enqueue(null, config.paths.base + entry);
 			}
+
+			if (route.page) {
+				// TODO use setting from layouts, in https://github.com/sveltejs/kit/pull/6197
+				// const nodes = await Promise.all(
+				// 	[...route.page.layouts, route.page.leaf].map((n) => {
+				// 		if (n !== undefined) return manifest._.nodes[n]();
+				// 	})
+				// );
+
+				// let prerender = config.prerender.default;
+				// for (const node of nodes) {
+				// 	prerender = node?.shared?.prerender ?? node?.server?.prerender ?? prerender;
+				// }
+
+				const leaf = await manifest._.nodes[route.page.leaf]();
+				let prerender =
+					leaf.shared?.prerender ?? leaf.server?.prerender ?? config.prerender.default;
+
+				prerender_map.set(route.id, prerender);
+			}
+		} catch (e) {
+			// We failed to import the module, which indicates it can't be prerendered
+			// TODO should we catch these? It's almost certainly a bug in the app
+			console.error(e);
 		}
+	}
 
-		await q.done();
+	for (const entry of config.prerender.entries) {
+		if (entry === '*') {
+			for (const [id, prerender] of prerender_map) {
+				if (prerender) {
+					if (id.includes('[')) continue;
+					const path = `/${id.split('/').filter(affects_path).join('/')}`;
+					enqueue(null, config.paths.base + path);
+				}
+			}
+		} else {
+			enqueue(null, config.paths.base + entry);
+		}
 	}
 
+	await q.done();
+
 	const rendered = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
 		getClientAddress,
 		prerendering: {
@@ -371,7 +424,7 @@ export async function prerender() {
 	mkdirp(dirname(file));
 	writeFileSync(file, await rendered.text());
 
-	output_and_exit(prerendered);
+	output_and_exit({ prerendered, prerender_map });
 }
 
 /** @return {string} */
diff --git a/packages/kit/src/core/sync/write_ambient.js b/packages/kit/src/core/sync/write_ambient.js
index 9904a1cee..bcb50dbc6 100644
--- a/packages/kit/src/core/sync/write_ambient.js
+++ b/packages/kit/src/core/sync/write_ambient.js
@@ -1,10 +1,43 @@
+import fs from 'fs';
 import path from 'path';
 import { get_env } from '../../exports/vite/utils.js';
 import { GENERATED_COMMENT } from '../../constants.js';
-import { create_types } from '../env.js';
+import { create_dynamic_types, create_static_types } from '../env.js';
 import { write_if_changed } from './utils.js';
+import { fileURLToPath } from 'url';
 
-const types_reference = '/// <reference types="@sveltejs/kit" />\n\n';
+const descriptions_dir = fileURLToPath(new URL('../../../scripts/special-types', import.meta.url));
+
+/** @param {string} filename */
+function read_description(filename) {
+	const content = fs.readFileSync(`${descriptions_dir}/${filename}`, 'utf8');
+	return `/**\n${content
+		.trim()
+		.split('\n')
+		.map((line) => ` * ${line}`)
+		.join('\n')}\n */`;
+}
+
+/**
+ * @param {{ public: Record<string, string>, private: Record<string, string> }} env
+ */
+const template = (env) => `
+${GENERATED_COMMENT}
+
+/// <reference types="@sveltejs/kit" />
+
+${read_description('$env+static+private.md')}
+${create_static_types('$env/static/private', env.private)}
+
+${read_description('$env+static+public.md')}
+${create_static_types('$env/static/public', env.public)}
+
+${read_description('$env+dynamic+private.md')}
+${create_dynamic_types('$env/dynamic/private', env.private)}
+
+${read_description('$env+dynamic+public.md')}
+${create_dynamic_types('$env/dynamic/public', env.public)}
+`;
 
 /**
  * Writes ambient declarations including types reference to @sveltejs/kit,
@@ -16,12 +49,5 @@ const types_reference = '/// <reference types="@sveltejs/kit" />\n\n';
 export function write_ambient(config, mode) {
 	const env = get_env(config.env, mode);
 
-	write_if_changed(
-		path.join(config.outDir, 'ambient.d.ts'),
-		GENERATED_COMMENT +
-			types_reference +
-			create_types('$env/static/public', env.public) +
-			'\n\n' +
-			create_types('$env/static/private', env.private)
-	);
+	write_if_changed(path.join(config.outDir, 'ambient.d.ts'), template(env));
 }
diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js
index 7f56d9e46..97a325e52 100644
--- a/packages/kit/src/exports/vite/index.js
+++ b/packages/kit/src/exports/vite/index.js
@@ -97,6 +97,9 @@ function kit() {
 	/** @type {import('types').Prerendered} */
 	let prerendered;
 
+	/** @type {import('types').PrerenderMap} */
+	let prerender_map;
+
 	/** @type {import('types').BuildData} */
 	let build_data;
 
@@ -195,6 +198,7 @@ function kit() {
 		async config(config, config_env) {
 			vite_config_env = config_env;
 			svelte_config = await load_config();
+
 			env = get_env(svelte_config.kit.env, vite_config_env.mode);
 
 			// The config is created in build_server for SSR mode and passed inline
@@ -418,7 +422,6 @@ function kit() {
 						[
 							vite_config.build.outDir,
 							results_path,
-							manifest_path,
 							'' + verbose,
 							JSON.stringify({ ...env.private, ...env.public })
 						],
@@ -431,12 +434,16 @@ function kit() {
 						if (code) {
 							reject(new Error(`Prerendering failed with code ${code}`));
 						} else {
-							prerendered = JSON.parse(fs.readFileSync(results_path, 'utf8'), (key, value) => {
+							const results = JSON.parse(fs.readFileSync(results_path, 'utf8'), (key, value) => {
 								if (key === 'pages' || key === 'assets' || key === 'redirects') {
 									return new Map(value);
 								}
 								return value;
 							});
+
+							prerendered = results.prerendered;
+							prerender_map = new Map(results.prerender_map);
+
 							fulfil(undefined);
 						}
 					});
@@ -475,7 +482,7 @@ function kit() {
 
 				if (svelte_config.kit.adapter) {
 					const { adapt } = await import('../../core/adapt/index.js');
-					await adapt(svelte_config, build_data, prerendered, { log });
+					await adapt(svelte_config, build_data, prerendered, prerender_map, { log });
 				} else {
 					console.log(colors.bold().yellow('\nNo adapter specified'));
 
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 64c470be6..7c78fb29f 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -44,6 +44,23 @@ function update_scroll_positions(index) {
 	scroll_positions[index] = scroll_state();
 }
 
+/** @type {Record<string, true>} */
+let warned_about_attributes = {};
+
+function check_for_removed_attributes() {
+	const attrs = ['prefetch', 'noscroll', 'reload'];
+	for (const attr of attrs) {
+		if (document.querySelector(`[sveltekit\\:${attr}]`)) {
+			if (!warned_about_attributes[attr]) {
+				warned_about_attributes[attr] = true;
+				console.error(
+					`The sveltekit:${attr} attribute has been replaced with data-sveltekit-${attr}`
+				);
+			}
+		}
+	}
+}
+
 /**
  * @param {{
  *   target: Element;
@@ -271,6 +288,8 @@ export function create_client({ target, base, trailing_slash }) {
 				};
 				root.$set(navigation_result.props);
 				tick().then(() => (console.warn = warn));
+
+				check_for_removed_attributes();
 			} else {
 				root.$set(navigation_result.props);
 			}
@@ -369,6 +388,8 @@ export function create_client({ target, base, trailing_slash }) {
 				hydrate: true
 			});
 			console.warn = warn;
+
+			check_for_removed_attributes();
 		} else {
 			root = new Root({
 				target,
@@ -1138,7 +1159,7 @@ export function create_client({ target, base, trailing_slash }) {
 			/** @param {Event} event */
 			const trigger_prefetch = (event) => {
 				const a = find_anchor(event);
-				if (a && a.href && a.hasAttribute('sveltekit:prefetch')) {
+				if (a && a.href && a.hasAttribute('data-sveltekit-prefetch')) {
 					prefetch(get_href(a));
 				}
 			};
@@ -1195,7 +1216,7 @@ export function create_client({ target, base, trailing_slash }) {
 				if (
 					a.hasAttribute('download') ||
 					rel.includes('external') ||
-					a.hasAttribute('sveltekit:reload')
+					a.hasAttribute('data-sveltekit-reload')
 				) {
 					return;
 				}
@@ -1222,7 +1243,7 @@ export function create_client({ target, base, trailing_slash }) {
 
 				navigate({
 					url,
-					scroll: a.hasAttribute('sveltekit:noscroll') ? scroll_state() : null,
+					scroll: a.hasAttribute('data-sveltekit-noscroll') ? scroll_state() : null,
 					keepfocus: false,
 					redirect_chain: [],
 					details: {
diff --git a/packages/kit/src/runtime/env-private.js b/packages/kit/src/runtime/env-private.js
index 9fdd71a7f..7352ceb17 100644
--- a/packages/kit/src/runtime/env-private.js
+++ b/packages/kit/src/runtime/env-private.js
@@ -1,4 +1,3 @@
-/** @type {App.PrivateEnv} */
 export let env = {};
 
 /** @type {(environment: Record<string, string>) => void} */
diff --git a/packages/kit/src/runtime/env-public.js b/packages/kit/src/runtime/env-public.js
index 0b8c28cc6..481a87046 100644
--- a/packages/kit/src/runtime/env-public.js
+++ b/packages/kit/src/runtime/env-public.js
@@ -1,4 +1,3 @@
-/** @type {App.PublicEnv} */
 export let env = {};
 
 /** @type {(environment: Record<string, string>) => void} */
diff --git a/packages/kit/src/runtime/server/endpoint.js b/packages/kit/src/runtime/server/endpoint.js
index 2f25bf010..9585e027e 100644
--- a/packages/kit/src/runtime/server/endpoint.js
+++ b/packages/kit/src/runtime/server/endpoint.js
@@ -4,9 +4,10 @@ import { check_method_names, method_not_allowed } from './utils.js';
 /**
  * @param {import('types').RequestEvent} event
  * @param {import('types').SSREndpoint} mod
+ * @param {import('types').SSRState} state
  * @returns {Promise<Response>}
  */
-export async function render_endpoint(event, mod) {
+export async function render_endpoint(event, mod, state) {
 	const method = /** @type {import('types').HttpMethod} */ (event.request.method);
 
 	// TODO: Remove for 1.0
@@ -22,6 +23,16 @@ export async function render_endpoint(event, mod) {
 		return method_not_allowed(mod, method);
 	}
 
+	const prerender = mod.prerender ?? state.prerender_default;
+
+	if (prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
+		throw new Error('Cannot prerender endpoints that have mutative methods');
+	}
+
+	if (state.prerendering && !prerender) {
+		throw new Error(`${event.routeId} is not prerenderable`);
+	}
+
 	try {
 		const response = await handler(
 			/** @type {import('types').RequestEvent<Record<string, any>>} */ (event)
@@ -34,6 +45,11 @@ export async function render_endpoint(event, mod) {
 			);
 		}
 
+		if (state.prerendering) {
+			response.headers.set('x-sveltekit-routeid', /** @type {string} */ (event.routeId));
+			response.headers.set('x-sveltekit-prerender', String(prerender));
+		}
+
 		return response;
 	} catch (error) {
 		if (error instanceof HttpError) {
diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js
index 5c03fb045..569a70534 100644
--- a/packages/kit/src/runtime/server/index.js
+++ b/packages/kit/src/runtime/server/index.js
@@ -65,7 +65,7 @@ export async function respond(request, options, state) {
 	}
 
 	const is_data_request = decoded.endsWith(DATA_SUFFIX);
-	if (is_data_request) decoded = decoded.slice(0, -DATA_SUFFIX.length);
+	if (is_data_request) decoded = decoded.slice(0, -DATA_SUFFIX.length) || '/';
 
 	if (!state.prerendering?.fallback) {
 		const matchers = await options.manifest._.matchers();
@@ -240,7 +240,7 @@ export async function respond(request, options, state) {
 					} else if (route.page) {
 						response = await render_page(event, route, route.page, options, state, resolve_opts);
 					} else if (route.endpoint) {
-						response = await render_endpoint(event, await route.endpoint());
+						response = await render_endpoint(event, await route.endpoint(), state);
 					} else {
 						// a route will always have a page or an endpoint, but TypeScript
 						// doesn't know that
diff --git a/packages/kit/src/runtime/server/page/fetch.js b/packages/kit/src/runtime/server/page/fetch.js
index e6ef5aae7..efef5f7b3 100644
--- a/packages/kit/src/runtime/server/page/fetch.js
+++ b/packages/kit/src/runtime/server/page/fetch.js
@@ -10,9 +10,10 @@ import { domain_matches, path_matches } from './cookie.js';
  *   options: import('types').SSROptions;
  *   state: import('types').SSRState;
  *   route: import('types').SSRRoute | import('types').SSRErrorPage;
+ *   prerender_default?: import('types').PrerenderOption;
  * }} opts
  */
-export function create_fetch({ event, options, state, route }) {
+export function create_fetch({ event, options, state, route, prerender_default }) {
 	/** @type {import('./types').Fetched[]} */
 	const fetched = [];
 
@@ -135,6 +136,7 @@ export function create_fetch({ event, options, state, route }) {
 				new Request(new URL(requested, event.url).href, { ...opts }),
 				options,
 				{
+					prerender_default,
 					...state,
 					initiator: route
 				}
diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js
index d350441a7..7e50694a3 100644
--- a/packages/kit/src/runtime/server/page/index.js
+++ b/packages/kit/src/runtime/server/page/index.js
@@ -51,8 +51,6 @@ export async function render_page(event, route, page, options, state, resolve_op
 		}
 	}
 
-	const { fetcher, fetched, cookies } = create_fetch({ event, options, state, route });
-
 	try {
 		const nodes = await Promise.all([
 			// we use == here rather than === because [undefined] serializes as "[null]"
@@ -109,24 +107,31 @@ export async function render_page(event, route, page, options, state, resolve_op
 		// it's crucial that we do this before returning the non-SSR response, otherwise
 		// SvelteKit will erroneously believe that the path has been prerendered,
 		// causing functions to be omitted from the manifesst generated later
+		// TODO incorporate layout options in https://github.com/sveltejs/kit/pull/6197
 		const should_prerender =
 			leaf_node.shared?.prerender ?? leaf_node.server?.prerender ?? options.prerender.default;
 		if (should_prerender) {
 			const mod = leaf_node.server;
 			if (mod && (mod.POST || mod.PUT || mod.DELETE || mod.PATCH)) {
-				throw new Error('Cannot prerender pages that have endpoints with mutative methods');
+				throw new Error('Cannot prerender pages that have mutative methods');
 			}
 		} else if (state.prerendering) {
 			// if the page isn't marked as prerenderable (or is explicitly
 			// marked NOT prerenderable, if `prerender.default` is `true`),
 			// then bail out at this point
-			if (!should_prerender) {
-				return new Response(undefined, {
-					status: 204
-				});
-			}
+			return new Response(undefined, {
+				status: 204
+			});
 		}
 
+		const { fetcher, fetched, cookies } = create_fetch({
+			event,
+			options,
+			state,
+			route,
+			prerender_default: should_prerender
+		});
+
 		if (!resolve_opts.ssr) {
 			return await render_response({
 				branch: [],
diff --git a/packages/kit/src/utils/routing.js b/packages/kit/src/utils/routing.js
index 67c6e0c6a..3ff8c9244 100644
--- a/packages/kit/src/utils/routing.js
+++ b/packages/kit/src/utils/routing.js
@@ -96,15 +96,6 @@ export function affects_path(segment) {
 	return !/^\([^)]+\)$/.test(segment);
 }
 
-/**
- * Turns a route ID into a path, if possible
- * @param {string} id
- */
-export function get_path(id) {
-	if (id.includes('[')) return null;
-	return `/${id.split('/').filter(affects_path).join('/')}`;
-}
-
 /**
  * @param {RegExpMatchArray} match
  * @param {string[]} names
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index ebde74cb4..68b4da04c 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -10,14 +10,10 @@
  * 	interface PageData {}
  *
  * 	interface Platform {}
- *
- * 	interface PrivateEnv {}
- *
- * 	interface PublicEnv {}
  * }
  * ```
  *
- * By populating these interfaces, you will gain type safety when using `env`, `event.locals` and `event.platform`.
+ * By populating these interfaces, you will gain type safety when using `event.locals`, `event.platform`, and `data` from `load` functions.
  *
  * Note that since it's an ambient declaration file, you have to be careful when using `import` statements. Once you add an `import`
  * at the top level, the declaration file is no longer considered ambient and you lose access to these typings in other files.
@@ -60,16 +56,6 @@ declare namespace App {
 	 * If your adapter provides [platform-specific context](https://kit.svelte.dev/docs/adapters#supported-environments-platform-specific-context) via `event.platform`, you can specify it here.
 	 */
 	export interface Platform {}
-
-	/**
-	 * The interface that defines the dynamic environment variables exported from `$env/dynamic/private`.
-	 */
-	export interface PrivateEnv extends Record<string, string> {}
-
-	/**
-	 * The interface that defines the dynamic environment variables exported from `$env/dynamic/public`.
-	 */
-	export interface PublicEnv extends Record<string, string> {}
 }
 
 /**
@@ -94,39 +80,6 @@ declare module '$app/environment' {
 	export const prerendering: boolean;
 }
 
-/**
- * This module provides access to runtime environment variables, as defined by the platform you're running on. For example
- * if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) (or running
- * [`vite preview`](https://kit.svelte.dev/docs/cli)), this is equivalent to `process.env`. This module only includes
- * variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#kit-env-publicprefix).
- *
- * This module cannot be imported into client-side code.
- *
- * ```ts
- * import { env } from '$env/dynamic/private';
- * console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
- * ```
- */
-declare module '$env/dynamic/private' {
-	export let env: App.PrivateEnv;
-}
-
-/**
- * Similar to [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), but only includes
- * variables that begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#kit-env-publicprefix)
- * (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code
- *
- * Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests — when possible, use `$env/static/public` instead.
- *
- * ```ts
- * import { env } from '$env/dynamic/public';
- * console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
- * ```
- */
-declare module '$env/dynamic/public' {
-	export let env: App.PublicEnv;
-}
-
 /**
  * ```ts
  * import {
@@ -169,7 +122,7 @@ declare module '$app/navigation' {
 	 *  1. ensuring that the code for the page is loaded, and
 	 *  2. calling the page's load function with the appropriate options.
 	 *
-	 * This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with `sveltekit:prefetch`.
+	 * This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with `data-sveltekit-prefetch`.
 	 * If the next navigation is to `href`, the values returned from load will be used, making navigation instantaneous.
 	 * Returns a Promise that resolves when the prefetch is complete.
 	 *
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index a4b9cb024..f7525c71c 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -19,6 +19,8 @@ import {
 import { SSRNodeLoader, SSRRoute, ValidatedConfig } from './internal.js';
 import { HttpError, Redirect } from '../src/runtime/control.js';
 
+export { PrerenderOption } from './private.js';
+
 export interface Adapter {
 	name: string;
 	adapt(builder: Builder): MaybePromise<void>;
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index a5813e4f7..d94fc29ad 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -16,7 +16,13 @@ import {
 	ServerInitOptions,
 	SSRManifest
 } from './index.js';
-import { HttpMethod, MaybePromise, RequestOptions, TrailingSlash } from './private.js';
+import {
+	HttpMethod,
+	MaybePromise,
+	PrerenderOption,
+	RequestOptions,
+	TrailingSlash
+} from './private.js';
 
 export interface ServerModule {
 	Server: typeof InternalServer;
@@ -267,13 +273,13 @@ export interface SSRNode {
 	shared: {
 		load?: Load;
 		hydrate?: boolean;
-		prerender?: boolean;
+		prerender?: PrerenderOption;
 		router?: boolean;
 	};
 
 	server: {
 		load?: ServerLoad;
-		prerender?: boolean;
+		prerender?: PrerenderOption;
 		POST?: Action;
 		PATCH?: Action;
 		PUT?: Action;
@@ -333,7 +339,9 @@ export interface PageNodeIndexes {
 	leaf: number;
 }
 
-export type SSREndpoint = Partial<Record<HttpMethod, RequestHandler>>;
+export type SSREndpoint = Partial<Record<HttpMethod, RequestHandler>> & {
+	prerender?: PrerenderOption;
+};
 
 export interface SSRRoute {
 	id: string;
@@ -352,6 +360,11 @@ export interface SSRState {
 	initiator?: SSRRoute | SSRErrorPage;
 	platform?: any;
 	prerendering?: PrerenderOptions;
+	/**
+	 * When fetching data from a +server.js endpoint in `load`, the page's
+	 * prerender option is inherited by the endpoint, unless overridden
+	 */
+	prerender_default?: PrerenderOption;
 }
 
 export type StrictBody = string | Uint8Array;
diff --git a/packages/kit/types/private.d.ts b/packages/kit/types/private.d.ts
index f517091c2..12fe1323a 100644
--- a/packages/kit/types/private.d.ts
+++ b/packages/kit/types/private.d.ts
@@ -184,6 +184,10 @@ export interface PrerenderErrorHandler {
 
 export type PrerenderOnErrorValue = 'fail' | 'continue' | PrerenderErrorHandler;
 
+export type PrerenderOption = boolean | 'auto';
+
+export type PrerenderMap = Map<string, PrerenderOption>;
+
 export interface RequestOptions {
 	getClientAddress: () => string;
 	platform?: App.Platform;
diff --git a/sites/kit.svelte.dev/src/lib/docs/Contents.svelte b/sites/kit.svelte.dev/src/lib/docs/Contents.svelte
index 1b538565f..dbed3dfcf 100644
--- a/sites/kit.svelte.dev/src/lib/docs/Contents.svelte
+++ b/sites/kit.svelte.dev/src/lib/docs/Contents.svelte
@@ -82,7 +82,7 @@
 		{#each contents as section}
 			<li>
 				<a
-					sveltekit:prefetch
+					data-sveltekit-prefetch
 					class="section"
 					class:active={section.path === path}
 					href={section.path}
@@ -95,7 +95,7 @@
 					{#each section.sections as subsection}
 						<li>
 							<a
-								sveltekit:prefetch
+								data-sveltekit-prefetch
 								class="subsection"
 								class:active={subsection.path === path}
 								href={subsection.path}
@@ -109,7 +109,7 @@
 									{#each subsection.sections as subsection}
 										<li>
 											<a
-												sveltekit:prefetch
+												data-sveltekit-prefetch
 												class="nested subsection"
 												class:active={subsection.path === path}
 												href={subsection.path}
diff --git a/sites/kit.svelte.dev/src/lib/docs/server/index.js b/sites/kit.svelte.dev/src/lib/docs/server/index.js
index 59d18d09c..a8c492f2e 100644
--- a/sites/kit.svelte.dev/src/lib/docs/server/index.js
+++ b/sites/kit.svelte.dev/src/lib/docs/server/index.js
@@ -153,9 +153,7 @@ export async function read_file(dir, file) {
 				);
 
 				// preserve blank lines in output (maybe there's a more correct way to do this?)
-				html = `<div class="code-block">${
-					options.file ? `<h5>${options.file}</h5>` : ''
-				}${html.replace(/<div class='line'><\/div>/g, '<div class="line"> </div>')}</div>`;
+				html = html.replace(/<div class='line'><\/div>/g, '<div class="line"> </div>');
 			} else if (language === 'diff') {
 				const lines = source.split('\n').map((content) => {
 					let type = null;
@@ -170,23 +168,25 @@ export async function read_file(dir, file) {
 					};
 				});
 
-				html = `<div class="code-block"><pre class="language-diff"><code>${lines
+				html = `<pre class="language-diff"><code>${lines
 					.map((line) => {
 						if (line.type) return `<span class="${line.type}">${line.content}\n</span>`;
 						return line.content + '\n';
 					})
-					.join('')}</code></pre></div>`;
+					.join('')}</code></pre>`;
 			} else {
 				const plang = languages[language];
 				const highlighted = plang
 					? PrismJS.highlight(source, PrismJS.languages[plang], language)
 					: source.replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
 
-				html = `<div class="code-block">${
-					options.file ? `<h5>${options.file}</h5>` : ''
-				}<pre class='language-${plang}'><code>${highlighted}</code></pre></div>`;
+				html = `<pre class='language-${plang}'><code>${highlighted}</code></pre>`;
 			}
 
+			html = `<div class="code-block">${
+				options.file ? `<h5>${options.file}</h5>` : ''
+			}${html}</div>`;
+
 			type_regex.lastIndex = 0;
 
 			return html
diff --git a/sites/kit.svelte.dev/src/lib/search/SearchResultList.svelte b/sites/kit.svelte.dev/src/lib/search/SearchResultList.svelte
index 94530a59b..70b5bc611 100644
--- a/sites/kit.svelte.dev/src/lib/search/SearchResultList.svelte
+++ b/sites/kit.svelte.dev/src/lib/search/SearchResultList.svelte
@@ -37,7 +37,7 @@
 	{#each results as result, i}
 		<li>
 			<a
-				sveltekit:prefetch
+				data-sveltekit-prefetch
 				href={result.href}
 				on:click={() => dispatch('select', { href: result.href })}
 				data-has-node={result.node ? true : undefined}
diff --git a/sites/kit.svelte.dev/src/routes/+page.svelte b/sites/kit.svelte.dev/src/routes/+page.svelte
index 3aa00a046..a18aa397b 100644
--- a/sites/kit.svelte.dev/src/routes/+page.svelte
+++ b/sites/kit.svelte.dev/src/routes/+page.svelte
@@ -40,7 +40,7 @@
 				of an SPA
 			</p>
 
-			<a sveltekit:prefetch href="/docs" class="cta">read the docs</a>
+			<a data-sveltekit-prefetch href="/docs" class="cta">read the docs</a>
 		</div>
 
 		<div slot="three">
@@ -50,7 +50,7 @@
 				support and more
 			</p>
 
-			<a sveltekit:prefetch href="/docs" class="cta">read the docs</a>
+			<a data-sveltekit-prefetch href="/docs" class="cta">read the docs</a>
 		</div>
 
 		<div class="description" slot="what">
@@ -77,7 +77,7 @@ cd my-app
 npm install
 npm run dev -- --open</code
 				></pre>
-			<a sveltekit:prefetch href="/docs" class="cta">get started</a>
+			<a data-sveltekit-prefetch href="/docs" class="cta">get started</a>
 		</div>
 	</Blurb>
 </div>
diff --git a/sites/kit.svelte.dev/src/routes/content.json/+server.js b/sites/kit.svelte.dev/src/routes/content.json/+server.js
index a0333f13e..a757fd607 100644
--- a/sites/kit.svelte.dev/src/routes/content.json/+server.js
+++ b/sites/kit.svelte.dev/src/routes/content.json/+server.js
@@ -1,6 +1,8 @@
 import { content } from '$lib/search/content.server.js';
 import { json } from '@sveltejs/kit';
 
+export const prerender = true;
+
 /** @type {import('./$types').RequestHandler} */
 export function GET() {
 	return json({
diff --git a/sites/kit.svelte.dev/svelte.config.js b/sites/kit.svelte.dev/svelte.config.js
index f59272ed4..e46f95df7 100644
--- a/sites/kit.svelte.dev/svelte.config.js
+++ b/sites/kit.svelte.dev/svelte.config.js
@@ -6,8 +6,7 @@ const config = {
 		adapter: adapter(),
 
 		prerender: {
-			default: true,
-			entries: ['*', '/content.json']
+			default: true
 		}
 	}
 };


Test patch:
diff --git a/packages/adapter-static/test/apps/prerendered/src/routes/+page.js b/packages/adapter-static/test/apps/prerendered/src/routes/+page.js
new file mode 100644
index 000000000..868703b63
--- /dev/null
+++ b/packages/adapter-static/test/apps/prerendered/src/routes/+page.js
@@ -0,0 +1,5 @@
+/** @type {import('./$types').PageLoad} */
+export async function load({ fetch }) {
+	const res = await fetch('/endpoint/implicit.json');
+	return await res.json();
+}
diff --git a/packages/adapter-static/test/apps/prerendered/src/routes/+page.svelte b/packages/adapter-static/test/apps/prerendered/src/routes/+page.svelte
index 404fd7751..d92d424b6 100644
--- a/packages/adapter-static/test/apps/prerendered/src/routes/+page.svelte
+++ b/packages/adapter-static/test/apps/prerendered/src/routes/+page.svelte
@@ -1,1 +1,7 @@
-<h1>This page was prerendered</h1>
\ No newline at end of file
+<script>
+	/** @type {import('./$types').PageData} */
+	export let data;
+</script>
+
+<h1>This page was prerendered</h1>
+<p>answer: {data.answer}</p>
diff --git a/packages/adapter-static/test/apps/prerendered/src/routes/endpoint/explicit.json/+server.js b/packages/adapter-static/test/apps/prerendered/src/routes/endpoint/explicit.json/+server.js
new file mode 100644
index 000000000..fc4b44c58
--- /dev/null
+++ b/packages/adapter-static/test/apps/prerendered/src/routes/endpoint/explicit.json/+server.js
@@ -0,0 +1,8 @@
+import { json } from '@sveltejs/kit';
+
+export const prerender = true;
+
+/** @type {import('./$types').RequestHandler} */
+export function GET() {
+	return json({ answer: 42 });
+}
diff --git a/packages/adapter-static/test/apps/prerendered/src/routes/endpoint/implicit.json/+server.js b/packages/adapter-static/test/apps/prerendered/src/routes/endpoint/implicit.json/+server.js
new file mode 100644
index 000000000..b579cdd18
--- /dev/null
+++ b/packages/adapter-static/test/apps/prerendered/src/routes/endpoint/implicit.json/+server.js
@@ -0,0 +1,9 @@
+import { json } from '@sveltejs/kit';
+
+// no export const prerender here, it should be prerendered by virtue
+// of being fetched from a prerendered page
+
+/** @type {import('./$types').RequestHandler} */
+export function GET() {
+	return json({ answer: 42 });
+}
diff --git a/packages/adapter-static/test/test.js b/packages/adapter-static/test/test.js
index 97bef6e5d..4069190d2 100644
--- a/packages/adapter-static/test/test.js
+++ b/packages/adapter-static/test/test.js
@@ -7,9 +7,18 @@ run('prerendered', (test) => {
 		assert.ok(fs.existsSync(`${cwd}/build/index.html`));
 	});
 
-	test('prerenders content', async ({ base, page }) => {
+	test('prerenders a page', async ({ base, page }) => {
 		await page.goto(base);
 		assert.equal(await page.textContent('h1'), 'This page was prerendered');
+		assert.equal(await page.textContent('p'), 'answer: 42');
+	});
+
+	test('prerenders an unreferenced endpoint with explicit `prerender` setting', async ({ cwd }) => {
+		assert.ok(fs.existsSync(`${cwd}/build/endpoint/explicit.json`));
+	});
+
+	test('prerenders a referenced endpoint with implicit `prerender` setting', async ({ cwd }) => {
+		assert.ok(fs.existsSync(`${cwd}/build/endpoint/implicit.json`));
 	});
 });
 
diff --git a/packages/adapter-static/test/utils.js b/packages/adapter-static/test/utils.js
index 914f80ec4..5f7bd122e 100644
--- a/packages/adapter-static/test/utils.js
+++ b/packages/adapter-static/test/utils.js
@@ -38,7 +38,6 @@ export function run(app, callback) {
 				console.error(`---\nstdout:\n${e.stdout}`);
 				console.error(`---\nstderr:\n${e.stderr}`);
 				console.groupEnd();
-				assert.unreachable(e.message);
 			}
 
 			context.cwd = cwd;
diff --git a/packages/kit/test/apps/basics/src/routes/routing/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/+page.svelte
index 81b4fef41..61f62ec6f 100644
--- a/packages/kit/test/apps/basics/src/routes/routing/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/routing/+page.svelte
@@ -9,6 +9,6 @@
 <a href="http://localhost:{$page.url.searchParams.get('port')}">elsewhere</a>
 <a href="/static.json">static.json</a>
 
-<a href="/routing/b" sveltekit:reload>b</a>
+<a href="/routing/b" data-sveltekit-reload>b</a>
 
 <div class="hydrate-test" />
diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js
index 03da4032d..1e49285d9 100644
--- a/packages/kit/test/apps/basics/test/client.test.js
+++ b/packages/kit/test/apps/basics/test/client.test.js
@@ -672,12 +672,12 @@ test.describe.serial('Invalidation', () => {
 
 		await page.click('button');
 		await page.waitForLoadState('networkidle');
-		await page.waitForTimeout(0); // apparently necessary
+		await page.waitForTimeout(200); // apparently necessary
 		expect(await page.textContent('h1')).toBe('a: 2, b: 3');
 
 		await page.click('button');
 		await page.waitForLoadState('networkidle');
-		await page.waitForTimeout(0);
+		await page.waitForTimeout(200);
 		expect(await page.textContent('h1')).toBe('a: 4, b: 5');
 	});
 });
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index c659740d2..c409e8f92 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -554,7 +554,7 @@ test.describe('Errors', () => {
 		expect(await page.textContent('h1')).toBe('500');
 
 		expect(await page.textContent('#message')).toBe(
-			'This is your custom error page saying: "Cannot prerender pages that have endpoints with mutative methods"'
+			'This is your custom error page saying: "Cannot prerender pages that have mutative methods"'
 		);
 	});
 
@@ -1427,7 +1427,7 @@ test.describe('Routing', () => {
 		expect(await page.textContent('body')).toBe('ok');
 	});
 
-	test('does not attempt client-side navigation to links with sveltekit:reload', async ({
+	test('does not attempt client-side navigation to links with data-sveltekit-reload', async ({
 		baseURL,
 		page
 	}) => {
diff --git a/packages/kit/test/apps/options/source/pages/prefetching/+page.svelte b/packages/kit/test/apps/options/source/pages/prefetching/+page.svelte
index 07b287bef..4d55ffcdf 100644
--- a/packages/kit/test/apps/options/source/pages/prefetching/+page.svelte
+++ b/packages/kit/test/apps/options/source/pages/prefetching/+page.svelte
@@ -1,1 +1,1 @@
-<a sveltekit:prefetch href="/path-base/prefetching/prefetched">click me</a>
+<a data-sveltekit-prefetch href="/path-base/prefetching/prefetched">click me</a>
diff --git a/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/src/app.d.ts b/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/src/app.d.ts
index 4eac1d5c9..917a96399 100644
--- a/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/src/app.d.ts
+++ b/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/src/app.d.ts
@@ -4,6 +4,4 @@
 declare namespace App {
 	// interface Locals {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/kit/test/build-errors/apps/private-dynamic-env/src/app.d.ts b/packages/kit/test/build-errors/apps/private-dynamic-env/src/app.d.ts
index 4eac1d5c9..917a96399 100644
--- a/packages/kit/test/build-errors/apps/private-dynamic-env/src/app.d.ts
+++ b/packages/kit/test/build-errors/apps/private-dynamic-env/src/app.d.ts
@@ -4,6 +4,4 @@
 declare namespace App {
 	// interface Locals {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/kit/test/build-errors/apps/private-static-env-dynamic-import/src/app.d.ts b/packages/kit/test/build-errors/apps/private-static-env-dynamic-import/src/app.d.ts
index 4eac1d5c9..917a96399 100644
--- a/packages/kit/test/build-errors/apps/private-static-env-dynamic-import/src/app.d.ts
+++ b/packages/kit/test/build-errors/apps/private-static-env-dynamic-import/src/app.d.ts
@@ -4,6 +4,4 @@
 declare namespace App {
 	// interface Locals {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/kit/test/build-errors/apps/private-static-env/src/app.d.ts b/packages/kit/test/build-errors/apps/private-static-env/src/app.d.ts
index 4eac1d5c9..917a96399 100644
--- a/packages/kit/test/build-errors/apps/private-static-env/src/app.d.ts
+++ b/packages/kit/test/build-errors/apps/private-static-env/src/app.d.ts
@@ -4,6 +4,4 @@
 declare namespace App {
 	// interface Locals {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/kit/test/prerendering/basics/src/routes/fetch-image/[...slug]/+server.js b/packages/kit/test/prerendering/basics/src/routes/fetch-image/[...slug]/+server.js
index 6d3a67e8e..e1494d291 100644
--- a/packages/kit/test/prerendering/basics/src/routes/fetch-image/[...slug]/+server.js
+++ b/packages/kit/test/prerendering/basics/src/routes/fetch-image/[...slug]/+server.js
@@ -1,5 +1,7 @@
 import * as fs from 'fs';
 
+export const prerender = true;
+
 export async function GET({ params }) {
 	const slug = params.slug.split('/');
 	const extension = slug[0].split('.').pop();
diff --git a/packages/kit/test/prerendering/basics/src/routes/origin/+page.server.js b/packages/kit/test/prerendering/basics/src/routes/origin/+page.server.js
index 984bab08b..beb4362c1 100644
--- a/packages/kit/test/prerendering/basics/src/routes/origin/+page.server.js
+++ b/packages/kit/test/prerendering/basics/src/routes/origin/+page.server.js
@@ -1,3 +1,5 @@
+export const prerender = true;
+
 export async function load({ url }) {
 	const res = await fetch(new URL('/origin/message.json', url.origin).href);
 	const { message } = await res.json();
diff --git a/packages/kit/test/prerendering/basics/src/routes/origin/message.json/+server.js b/packages/kit/test/prerendering/basics/src/routes/origin/message.json/+server.js
index 2e53051d8..04e6ce42c 100644
--- a/packages/kit/test/prerendering/basics/src/routes/origin/message.json/+server.js
+++ b/packages/kit/test/prerendering/basics/src/routes/origin/message.json/+server.js
@@ -1,5 +1,9 @@
 import { json } from '@sveltejs/kit';
 
+// TODO remove this when we're able to replace the global `fetch` call in the
+// neighbouring `+page.server.js` with SvelteKit's `fetch`
+export const prerender = true;
+
 export function GET() {
 	return json({
 		message: 'hello'
diff --git a/packages/kit/test/prerendering/basics/src/routes/page-server-options/+page.server.js b/packages/kit/test/prerendering/basics/src/routes/page-server-options/+page.server.js
deleted file mode 100644
index d43d0cd2a..000000000
--- a/packages/kit/test/prerendering/basics/src/routes/page-server-options/+page.server.js
+++ /dev/null
@@ -1,1 +0,0 @@
-export const prerender = false;
diff --git a/packages/kit/test/prerendering/basics/src/routes/page-server-options/+page.svelte b/packages/kit/test/prerendering/basics/src/routes/page-server-options/+page.svelte
deleted file mode 100644
index 3c3d6df7a..000000000
--- a/packages/kit/test/prerendering/basics/src/routes/page-server-options/+page.svelte
+++ /dev/null
@@ -1,1 +0,0 @@
-<h1>This should not be visible</h1>
diff --git a/packages/kit/test/prerendering/basics/test/test.js b/packages/kit/test/prerendering/basics/test/test.js
index 29a62b3ce..44d80bfff 100644
--- a/packages/kit/test/prerendering/basics/test/test.js
+++ b/packages/kit/test/prerendering/basics/test/test.js
@@ -112,11 +112,6 @@ test('does not prerender page with shadow endpoint with non-load handler', () =>
 	assert.ok(!fs.existsSync(`${build}/shadowed-post/__data.js`));
 });
 
-test('does not prerender page with prerender = false in +page.server.js', () => {
-	assert.ok(!fs.existsSync(`${build}/page-server-options.html`));
-	assert.ok(!fs.existsSync(`${build}/page-server-options/__data.js`));
-});
-
 test('decodes paths when writing files', () => {
 	let content = read('encoding/path with spaces.html');
 	assert.ok(content.includes('<p id="a">path with spaces</p>'));
diff --git a/packages/kit/test/prerendering/trailing-slash/src/routes/standalone-endpoint.json/+server.js b/packages/kit/test/prerendering/trailing-slash/src/routes/standalone-endpoint.json/+server.js
index 82d3cb7b8..f8d1402d3 100644
--- a/packages/kit/test/prerendering/trailing-slash/src/routes/standalone-endpoint.json/+server.js
+++ b/packages/kit/test/prerendering/trailing-slash/src/routes/standalone-endpoint.json/+server.js
@@ -1,5 +1,7 @@
 import { json } from '@sveltejs/kit';
 
+export const prerender = true;
+
 export async function GET() {
 	return json({ answer: 42 });
 }
diff --git a/packages/kit/test/prerendering/trailing-slash/svelte.config.js b/packages/kit/test/prerendering/trailing-slash/svelte.config.js
index de260fe7c..41249f6dd 100644
--- a/packages/kit/test/prerendering/trailing-slash/svelte.config.js
+++ b/packages/kit/test/prerendering/trailing-slash/svelte.config.js
@@ -1,5 +1,7 @@
 import adapter from '../../../../adapter-static/index.js';
 
+export const prerender = true;
+
 /** @type {import('@sveltejs/kit').Config} */
 const config = {
 	kit: {
@@ -8,8 +10,7 @@ const config = {
 		}),
 
 		prerender: {
-			default: true,
-			entries: ['*', '/standalone-endpoint.json']
+			default: true
 		},
 
 		trailingSlash: 'always'