惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News: Ask HN
Hacker News: Ask HN
IT之家
IT之家
S
SegmentFault 最新的问题
T
Tailwind CSS Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美
J
Java Code Geeks
博客园 - 聂微东
雷峰网
雷峰网
阮一峰的网络日志
阮一峰的网络日志
The Cloudflare Blog
博客园_首页
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 【当耐特】
腾讯CDC
Apple Machine Learning Research
Apple Machine Learning Research
酷 壳 – CoolShell
酷 壳 – CoolShell
V
V2EX
宝玉的分享
宝玉的分享
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
月光博客
月光博客
NISL@THU
NISL@THU
T
The Exploit Database - CXSecurity.com
C
CXSECURITY Database RSS Feed - CXSecurity.com
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
Blog — PlanetScale
Blog — PlanetScale
aimingoo的专栏
aimingoo的专栏
L
LINUX DO - 热门话题
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
F
Fortinet All Blogs
博客园 - Franky
L
Lohrmann on Cybersecurity
S
Secure Thoughts
量子位
V
Vulnerabilities – Threatpost
Last Week in AI
Last Week in AI
博客园 - 叶小钗
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
LINUX DO - 最新话题
I
InfoQ
C
CERT Recently Published Vulnerability Notes
Security Archives - TechRepublic
Security Archives - TechRepublic
P
Proofpoint News Feed
G
GRAHAM CLULEY
Cisco Talos Blog
Cisco Talos Blog

Josh Collinsworth

LLMs and performative productivity Man Cereal My review of the Nüborn Baby at 3 months 2025 Year in Review AI optimism is a class privilege Alchemy Titles matter The blissful zen of a good side project Goodbye, Griff. You were a good boy. Rare words in common phrases, and how to avoid getting them wrong Things I enjoyed in 2024 The childlike and the childish A response to "Defending Open Source: Protecting the Future of WordPress" If WordPress is to survive, Matt Mullenweg must be removed For whatever it's worth: my advice on job hunting in tech A decade of code Follow-up: the Glove80 after six months The quiet, pervasive devaluation of frontend I worry our Copilot is leaving some passengers behind Things I enjoyed in 2023 First impressions of the MoErgo Glove80 ergonomic keyboard A message from the Captain of the S.S. Layoff Things you forgot (or never knew) because of React Alfred vs. Raycast: my constant debate Adding page transitions in SvelteKit Ten tips for better CSS transitions and animations Understanding easing and cubic-bezier curves in CSS Impressions of the ZSA Moonlander at one month Why you should never use px to set font-size in CSS Forty-two The self-fulfilling prophecy of React Announcing Hondo Building accessible toggle buttons (with examples for Svelte, Vue, and React) Debugging iOS Safari (when all you have is a Mac) Creating dynamic bar charts with CSS grid Let's learn SvelteKit by building a static Markdown blog from scratch Adding blog comments to your static site with utterances Converting from Gridsome to SvelteKit Introducing Svelte, and Comparing Svelte with React and Vue Goodbye, WordPress Announcing Quina (My First App)! How to Create Custom Editor Blocks with Block Lab A New Headless Site with Gridsome This isn't the Time, But it's the Perfect Time; Goodbye, Instagram How to Connect Local with CodeKit How to Check Uniqueness in an Array of Objects in JavaScript Adding Gutenberg Full- and Wide-Width Image Support to Your WordPress Theme Let's Learn CSS Variables! New Site, New Theme for 2018 Five Ways to Become a Better Designer (That Aren't Design) My Essential Tools for WordPress Development The Five Things I Wish Somebody Had Told Me as a Design Student WordPress Child Theme Explanation and Walkthrough Why Designers Shouldn't "Fix" Other Designers' Logos 8 Mistakes to Avoid in Your Student Design Portfolio Profit is Not a Value Understanding the Difference Between Image and Vector File Types Pantone, Color, and What I Wish I Had Known Sooner as a Designer Social Media, Compulsion, and the 12 Things I Learned on My Break from Facebook Classic rock, Mario Kart, and why we can't agree on Tailwind
Breaking changes in SvelteKit, August 2022
2022-08-17 · via Josh Collinsworth

Published: August 17, 2022
Last updated: August 19, 2022

SvelteKit introduced a new syntax for its routing, as well as for its data loading, in the newest pre-1.0 release. It’s a complete overhaul of how src/routes works (from pages to endpoints), as well as a significant change to how and where you can use SvelteKit’s powerful load function.

If you were on an older version of SvelteKit prior to the new release (August 16, 2022), you’ll probably need to make some serious changes to your file structure and code before you can update your version of SvelteKit.

This guide will walk you through the technical steps you need to go through in order to make the changes. It contains a handy npx script you can run that will make many of the necessary changes for you, and will highlight in your code where you need to make further updates manually.

The migration tool is very nice. However, it likely can’t fix everything for you automatically; you’ll probably be left with a list of to-dos after running it.

This post will help cover what’s changed, and how to convert the syntax and structure from the old to the new.

Let’s dig in.

Routing syntax

Previously, SvelteKit routes could be either files or folders. For example, this would be how you’d structure a site with /about, /blog, and /blog/post-title pages:

-- Old routing structure:

📂 src
┗ 📂 routes
  ┣ 📜 index.svelte
  ┣ 📜 about.svelte
  ┗ 📂 blog
    ┣ 📜 index.svelte
    ┗ 📜 post-title.md

.md files won’t work in SvelteKit out of the box; it requires some setup. This is just to show an example of how you might structure a blog.

In the latest update, every route must be inside a folder; no more index files or files named for the page they represent.

Instead, the route will take the folder’s name, and a new +page.svelte component is the contents of the page:

-- New routing structure:

📂 src
┗ 📂 routes
  ┣ 📜 +page.svelte
  ┣ 📂 about
  ┃ ┗ 📜 +page.svelte
  ┗ 📂 blog
    ┣ 📜 +page.svelte
    ┗ 📂 post-title
      ┗ 📜 +page.md

If you think that looks worse: I agree with you. It’s much messier, and further away from the routing structure we’ve come to know from PHP, and other meta frameworks.

However, give it a chance; there’s good reason for the changes. +page isn’t the only new file in this system. In fact, there are a handful of + files that have special meaning in the new routing system.

Plus, this makes page creation much more explicit. No route or page will exist unless there’s a corresponding + file. There are advantages to this we’ll cover later.

In fact, you actually can’t prefix a file or folder inside of src/routes with a +, as it’s now a special, reserved symbol.

Converting to the new routing syntax

Again, the migration guide and migration tool in it can do much of the work of converting for you. This is just to help understand the changes (in case you have to do anything manually).

  • Any index.* file becomes +page.* instead
  • Any named page goes inside a folder named for the page slug, and the file gets renamed to +page.svelte.

Example:

  • src/routes/index.svelte now becomes src/routes/+page.svelte
  • src/routes/about.svelte now becomes src/routes/about/+page.svelte

If you were previously plopping a bunch of Markdown files together in the same folder—or using a lot of named page files—this probably sounds incredibly daunting.

For pages, you may have to just bite the bullet and tuck all those files into their own folders.

For posts, however, rather than changing all of their names and nesting them in their own individual folders, you can convert to a dynamic route, and load the files indirectly. That requires some setup in the site’s code, but it saves renaming and relocating all your Markdown files.

A dynamic route is wrapped in braces ([]), and uses its +page.svelte file (and probably a load function) to dynamically load the proper post. That would look something like this:

📂 src
┗ 📂 routes
  ┗ 📂 blog
    ┣ 📜 (a bunch of markdown files here)
    ┗ 📂 [slug]
      ┗ +page.svelte

Within the dynamic [slug]/+page.svelte file, you can grab whichever markdown file corresponds with slug, and render its contents.

It’s a little weird to explain, but there’s more on that in my SvelteKit Markdown Blog post. Also, see the routing docs. I only mention it here so you know you have options that don’t require creating specially named folders for each of your individual posts.

Layouts

Layouts fortunately haven’t changed much. The only real difference is that now, instead of beginning with __, layout files also begin with +.

-- Old layout naming:

📂 src
┗ 📂 routes
  ┗ 📜 __layout.svelte
-- New layout naming:

📂 src
┗ 📂 routes
  ┗ 📜 +layout.svelte

That means the conversion is pretty much as simple as changing the name of the file from __layout.svelte to +layout.svelte—although depending on what you were doing inside the layout, there may be more to it. More on that in a bit.

Also worth noting: the same applies to __error.svelte; it can simply be changed to +error.svelte instead.

Previously, any file or folder inside of src/routes could be “hidden” from the router by prefixing its name with an underscore (_). For example, a folder full of Markdown posts you intended to load with a dynamic route, or perhaps a component you only intended to import:

-- Old hidden file/folder structure:

📂 src
┗ 📂 routes
  ┣ 📁 _hidden-folder
  ┗ 📜 _hidden-component.svelte

One big benefit of the new + file system is that we no longer need to explicitly hide files and folders from the router. This lets us colocate files related to a specific route within that route’s folder, without worrying about inadvertently creating new routes.

In the new routing system, we opt in to routes, rather than selectively opting files and folders out.

Want a page there? Put a +page.svelte component in.

Want an endpoint? Make that a +server.js file instead. (More on those in a bit.)

In all other cases: that thing won’t get a route, even though it’s inside the routes folder, like it would have before. If it ain’t a + file, it ain’t gonna show up.

This means you don’t actually need to do any conversion here; a _hidden folder is still hidden. You just don’t need the underscore in front of its name anymore.

The load function

This is the other really big part of the newest breaking changes.

Previously, a load function went inside a second <script> tag in a page’s Svelte file, with a context="module" attribute separating that tag from the normal Svelte component <script> tag:

<!-- Old load function -->
<script context="module">
	export const load = () => {
		// Do stuff here
		return {
			props: {
				// The stuff to return
			}
		};
	};
</script>

<script>
	// Normal Svelte component stuff here
</script>

In the new system, there’s no more <script context="module">. In fact, the load function now lives in a file all its own, named either +page.js or +page.server.js. We’ll cover the difference between the two in a moment.

Either way, the file is colocated with a corresponding +page.svelte file, like so:

📂 src
┗ 📂 routes
  ┣ 📜 +page.svelte
  ┗ 📜 +page.js
       (or +page.server.js)

Before rendering the page, SvelteKit will run the load function found in +page.js (or +page.server.js) automatically, and pass whatever that function returns to the page component as a data prop.

Any .js files mentioned could also be .ts files instead, if using TypeScript.

Beyond that, everything is the same as before with the load function:

  • The load function must be exported;
  • load has access to parameters like url and params; and
  • load must return an object (which, again, is passed to the page as a prop, to be used in the template). (It does not need to pass the data using a prop property anymore, however.)

One other thing: to execute a load function on all pages, you’ll do so in a +layout.js file, which lives alongside the +layout.svelte file.

Converting from the old load function to the new

Depending on what your load function is doing, this may be a simple conversion, or it may require some finessing. But for starters:

  1. Anywhere you have a <script context="module"> tag in a Svelte page component, copy its contents into a +page.js (or +page.server.js) file, and place that file alongside the one you copied the function out of.

  2. Be sure your load function returns data. Previously, you needed to do so inside a props object; now you no longer need to. Whatever was inside of props can now just be the returned object.

  3. Modify your template files accordingly. Wherever the load function’s data was being consumed, you’ll probably need to update the variable names.

Data returned from the load function is available in the template as the data prop. So you now only export one prop, named data, and use it from there.

<script>
	// Data returned from `load` is automatically available as `data`
	export let data;
</script>

<article>
	<h1>{data.title}</h1>

	<p>{data.date}</p>
</article>

Differences between +page.js and +page.server.js

The main differences between the two are:

  • +page.js runs on both the server and the client (as the load function did previously).
  • +page.server.js runs on the server only. It can also respond to HTTP verbs.

Which to use will depend mainly on your use case. There’s one key thing to remember though, which is: fetch works differently in the two files!

  • In +page.js, you’ll pass fetch as a parameter, like this:

    // +page.js only
    import { json } from '@sveltejs/kit';
    
    export const load = ({ fetch }) => {
    	const myData = fetch('/relative/path/here');
    	return json(myData);
    };

That’s because the client and server have different versions of fetch; the Node version and the browser fetch API are not identical. So when you pass fetch as a parameter to a load function, SvelteKit does a bit of magic (and adds some niceties) to make sure your fetch call works, and works well, on both the server and client.

This is unnecessary when using +page.server.js, however, for obvious reasons; code that only runs on the server only needs the server version of fetch. (In fact, if you try to pass fetch as a parameter inside +page.server.js, you’ll get an error.)

This also means when using fetch on the server only, you’ll need to be explicit about the domain of the request; no relative paths. (Easy enough, with url.origin.) But you can use relative routes with the fetch helper in +page.js.

// +page.server only
import { json } from '@sveltejs/kit';

export const load = ({ url }) => {
	const myData = fetch(`${url.origin}/my/api/path`);
	return json(myData);
};

Server routes (API endpoints)

Previously, to create an endpoint, you would drop a .js (or .ts file) somewhere into src/routes, and include the data type it was meant to return as part of the name of that file.

For example: if you wanted to return a list of posts as JSON at the path /api/posts.json, this would be your folder structure:

-- Old endpoint structure:

📂 src
┗ 📂 routes
  ┗ 📂 api
    ┗ 📜 posts.json.js

The new structure is similar. By now, you’ve probably guessed the file we need begins with a +. In this case, it’s +server.js, and while we can have .json be a part of the path name, we don’t actually have to.

-- New endpoint structure:

📂 src
┗ 📂 routes
  ┗ 📂 api
    ┗ 📂 posts
      ┗ 📜 +server.js

Again, the posts folder could be named posts.json, if you wanted to keep the path the same. It really just depends what you want the route to look like when visited in the browser; there’s no practical difference. (In the new example above, the route would be /api/posts, with no filetype suffix; renaming the folder to posts.json would make the route /api/posts.json.)

Another change to be aware of: previously, SvelteKit handled setting the proper headers for our endpoint responses, as well as converting the data to the proper format. (It could do all this easily, since we were required to put the data type in the file name.)

// Previously:
export const get = () => {
	const message = 'Hello!';

	return {
		status: 200,
		body: {
			message
		}
	};
};

Now, however, server routes must return a proper Response object.

That would be a pain to do on our own, but fortunately, SvelteKit ships with a json function available to do all the conversion and everything for us. We just need to import it, then wrap whatever data we return in that function call.

// The new way:
import { json } from '@sveltejs/kit';

export const GET = () => {
	const message = 'Hello!';
	return json(message);
};

Even if you use the conversion script mentioned above, you’ll still need to adjust your endpoints’ responses; adjust relative file paths (since SvelteKit’s new routing syntax often requires nesting files a level deeper); and handle implementing the json() responses.

The only other big difference to be aware of—which you may have noticed already—is that the HTTP verb functions accepted by a server route (endpoint) now must be capitalized.

// Old:
export const get = () => {
	// Do the thing
};

// New:
export const GET = () => {
	// Do the thing
};

Oh, and one last thing: since we’re not returning an object with a status and a body anymore, we’ll want to import SvelteKit’s error function wherever we need to return a non-200 status.

// Old:
return {
	status: 400,
	body: new Error('not found')
};

// New:
import { error } from '@sveltejs/kit';

try {
	//return something here
} catch ({ message }) {
	throw error(400, message);
}

Converting server routes

As with pages, you may need to create new named folders for any server routes (API endpoints).

  • Any index files should be renamed to +server.js files;
  • Any named endpoint file should be put in a folder, named for the endpoint path, and be renamed to +server.js.

Example:

  • src/routes/api/index.json.js becomes src/routes/api/+server.js
  • src/routes/api/posts.json.js becomes src/routes/api/post/+server.js

You’ll also need to convert any HTTP verb function names to all-uppercase (get becomes GET, post becomes POST, etc.)

And rather than return a JavaScript object with a status and body, you’ll need to either use SvelteKit’s json helper (if returning JSON); or, return a new Response() constructor, which takes two arguments: body, and options.

Wrapup

That’s it for the major changes. Best of luck converting your SvelteKit sites!

Once more, be sure to check out the official migration guide for more details on converting your existing site. It’s got an excellent migration tool that will help do much of the hard work for you. It goes into much more detail than I have here on many things (especially including TypeScript and typing your code).

If I’ve left anything out, or made any mistakes, feel free to let me know in the comments.