kit
kit copied to clipboard
Enable package-provided routes
Describe the problem
Please bear with me for a minute, explaining the rationale requires a bit of context.
I'm working on a (FOSS) SvelteKit-based ebook library app. It is built to run on all platforms SvelteKit supports, with platform-specific integrations being used where possible. If you'd be running it on Cloudflare, for example, the Cloudflare R2 bucket integration will be used for storage; if running on Node, it will use the local filesystem. On a platform like Netlify, which has support for edge functions, book metadata parsing will run on the edge; whereas on other platforms, it'll use a web worker for that.
In addition to platform features, there are a bunch of other modules with configurable implementations (mailing, knowledge graph queries, image uploads, etc.).
Now, to make it easier for users to get started, as well as keeping runtime size small, I have an npm create package that will prompt the user for the services they use, and perform a custom install of all required packages: Say, @example/core, @example/mailing-mailgun, and @example/storage-cloudflare (and their respective dependencies).
All of this works; what I'm missing, however, is a good way of packaging up the actual SvelteKit app in a reusable way. Right now, I have to copy all project files from a template repository into the user's project directory, install dependencies, and generate a matching .env file.
On the plus side, this gives users something of an improved git clone, essentially a glorified configuration script. They can modify all routes and components to their liking.
On the downside, this setup is incredibly hard to reliably update, and requires anything and everything to have a stable API.
Describe the proposed solution
What I envision is the ability to distribute my SvelteKit app as a dependency users would install into an empty project, along with any extension packages they need. The project would start with an empty src/routes directory; all routes would be loaded from the base application, unless a file with the same path exists in the routes directory. For example, my app would provide an implicit /profiles/[user] route, which users could eject/override by creating src/routes/profiles/[user]/+page.svelte. (Thanks to @theetrain on Discord for putting it so succinctly). Obviously this still bears the risk of ejected routes breaking after updates, but in that case it's immediately clear whether a bug is caused by userland or package code.
This would make for a very small installation footprint, while still being hackable.
From a technical POV, this would probably work best as a Vite plugin that overlays SvelteKit, as in plugins: [ kit(), app() ]. I have dabbled with Vite, but definitely not enough yet to do this on my own, however.
Alternatives considered
Having users git clone a starter repository, or copying a starter project using the create package both work, but come with the aforementioned downsides (namely the complexity of updates and support headaches).
Importance
would make my life easier
Additional Information
The project is still a work in progress, although the repository shows the structure I'm going for, if that helps: https://github.com/project-kiosk/kiosk/tree/next#readme
Some way to expose routes from a package would be a nice enhancement for things like @auth/sveltekit to take advantage of. Currently it exposes all the api routes through a handler so they don't exist as routes in the route tree which isn't ideal when you want to split into multiple functions at route boundaries
This is something we've been missing as well for our Sveltekit app template and would love to see it implemented.
I would imagine that for something like this to work, the Sveltekit router would first have to be enhanced to also support non-file-based routing, which would consequently also help with localized routing.
This is something we're looking for too
I'd like to express my need for this too and believe this would enable a bunch of new use-cases by making svelte kit more composable.
In case there would be some interest by the maintainers to start conceptual work on this topic, I'd love to contribute. My impression from some explorations in svelte-kit to make this possible was, that it would not be very hard.
One way to open up the router e.g. could be to allow other vite-plugins accessing the internal manifest, extend it and offer a few convenience APIs, which could be equally composable as nuxt plugins/ modules.
Here a few interesting ways (imo) this is being handled by other frameworks at the moment:
Nuxt
Apps can be extended in different ways:
app/router.options.ts: https://nuxt.com/docs/guide/going-further/custom-routing
import type { RouterConfig } from '@nuxt/schema'
export default <RouterConfig> {
routes: (_routes) => [
{
name: 'home',
path: '/',
component: () => import('~/pages/home.vue').then(r => r.default || r)
}
],
}
- Using the
pages:extendhook: https://nuxt.com/docs/guide/going-further/custom-routing#using-the-pagesextend-hook
This might be very vue-oriented in terms of pattern (hooks), but I found the ability to hook into the setup process of nuxt very convincing and convenient. A plugin (what they call "module") registers itself as nuxt module which then calls defineNuxtConfig of each plugin. This makes it possible compose different plugins which add add different routes (thinking of a composable CMS e.g.).
The same pattern can be used to extend Nitro to add new API routes or route middlewares.
export default defineNuxtConfig({
hooks: {
'pages:extend' (pages) {
// add a route
pages.push({
name: 'profile',
path: '/profile',
file: '~/extra-pages/profile.vue'
})
// remove routes
function removePagesMatching (pattern: RegExp, pages: NuxtPage[] = []) {
const pagesToRemove = []
for (const page of pages) {
if (pattern.test(page.file)) {
pagesToRemove.push(page)
} else {
removePagesMatching(pattern, page.children)
}
}
for (const page of pagesToRemove) {
pages.splice(pages.indexOf(page), 1)
}
}
removePagesMatching(/\.ts$/, pages)
}
}
})
Fresh
In fresh plugins can add new routes or middlewares too: https://deno.com/blog/fresh-1.3#adding-routes-andor-middlewares-from-plugins
function myPlugin() {
return {
name: "my-plugin",
middlewares: [
{
middleware: { handler: () => new Response("Hello!") },
path: "/hello",
},
],
routes: [
{
path: "/admin/hello",
component: () => <p>Hello from /admin/hello</p>,
},
],
};
}
In a follow-up iteration they are planning to open this up even further: https://github.com/denoland/fresh/issues/1602#issuecomment-1671740190
const app = freshApp({
plugins: [
twindPlugin,
{
name: "my-inline-plugin",
setup(app) {
app.use(req => ...)
}
}
});
I just found another use case for this. As part of an application I'm working on, I created a fully spec-compliant OAuth authorization server in SvelteKit. This is really gruesome validation work, and something I wouldn't recommend doing unless you really know your way around authorization.
So in short: This is ideally suited to be distributed as a package, to enable other SvelteKit applications to implement OAuth support without requiring domain knowledge. Authorization servers need to expose a few routes, some of them API-only, others with user-facing forms; they need quite a bit of configuration, and database-persisted entities. This means you can't really distribute it as a middleware-only layer—people would need all kinds of customisation, and doing those as native SvelteKit routes and page handlers is a lot more developer-friendly than passing a heap of options to a middleware.
If I had a way to provide this as a package to others, the ecosystem would benefit as a whole.
@Rich-Harris maybe you can take another look at this issue at some point? 🙏
I am building reusable/publishable Smart UI components ( using Vercel AI SDK) that need associated server API (/api/ai/completions/+server.ts etc), I like to programmatically add those routes to host app.
Yes please, this is currently my #1 frustration with SvelteKit.
I'm building a bunch of smaller sites for clients which share a lot of routes (RSS, blog, checkout, auth etc). It would be great to not have to manually keep routes in sync across sites as I make changes and improvements to individual routes.
In general, it would be nice to have a programmatic way to define routes. This would allow libraries to export functions that take a svelte kit context as parameter and add routes to it. The function could be called by an app or a plugin to inject the routes.
It would also allow app developers to quickly create routes within a single file, instead of having to create a folder and a file +server.ts, for each route.
For those that are interested, this setup is roughly how https://github.com/evidence-dev/evidence is architected.
The user brings a small number of files and the rest are provided by the library.
In terms of implementation, it works around the described issue by scaffolding a hidden svelte kit app in a .evidence directory, and makes liberal use of fs.copyFileSync to put the user files into this app.
When the users runs the app, they actually are running the app in the hidden directory. https://github.com/evidence-dev/evidence/blob/next/packages/evidence/cli.js
Is there any plan to work on this feature?
For those that are interested, this setup is roughly how https://github.com/evidence-dev/evidence is architected.
The user brings a small number of files and the rest are provided by the library.
In terms of implementation, it works around the described issue by scaffolding a hidden svelte kit app in a .evidence directory, and makes liberal use of fs.copyFileSync to put the user files into this app.
When the users runs the app, they actually are running the app in the hidden directory. https://github.com/evidence-dev/evidence/blob/next/packages/evidence/cli.js
Ah, that's a neat solution. I might steal it!
Are there any plans for this?
Will this be obsolete with remote functions maybe?
Will this be obsolete with remote functions maybe?
No, because remote functions only offer to solvelibrary managed API endpoints but not page routes.
I've been considering this recently, and Claude suggested an interesting approach: a Vite plugin could generate the routes at build-time. Based on this inspiration, here's a wild idea how to allow shipping routes from packages...
The package maintainer:
- Puts routes in
lib/routesor some other standard location - Somehow specifies configuration options
- These routes get get compiled into a Vite plugin which injects the routes at build-time
The package user:
- Imports the Vite plugin from the package
- Adds the plugin to their Vite config and optionally configures it
- Enjoys their new routes
Configuration options are passed to the routes are runtime.
I think the approach is promising, even if there are open questions:
- Do we need some ability to some
hooks.server.tsthat only applies to these routes? How would it interop with the user's hooks? - Is there a need for some build-time config options? The main one I think about is a route prefix, which you almost certainly want to allow users to configure.
- How does this plugin interop with the SvelteKit plugin?