Introduction: Vite as Core Infrastructure
My name's Rich and I'm the creator of the Svelte framework, which I work on full-time at Vercel.
Now when I shared on Twitter that I would be speaking at this conference, I did so with a tweet comparing Vite to the United Nations because it's kind of a place where different projects can come together to advance the common good. But I actually think that misstates the role that Vite has in the ecosystem. I think it's something more fundamental than a deliberative body. I think of it as providing missing infrastructure in the JavaScript ecosystem. Infrastructure usually isn't very exciting or sexy, but it's what allows societies to be built and become prosperous. So Vite is less like the UN and more like, say, the Department of Transportation.
Now the thing about infrastructure is that it needs effective government and professional maintenance, which unfortunately is where a lot of open source projects fall down. And that's one of the reasons that I'm so excited that this conference is happening today. It's a mark of how well-supported and established Vite has become. And the diversity of projects that are represented here gives me a lot of hope that that will continue to be the case for a very long time, which is essential for those of us building things on top of Vite.
What Are Svelte and SvelteKit?
So about that. Like most of the speakers here today, my unstated purpose is to try and convince you that you should be using the thing that we're building on top of Vite. Vite and SvelteKit have been intertwined for a long time. In many ways, they've grown up together and have informed each other's design at critical points. Many people on the Svelte core team are also Vite contributors, so suffice to say that we're invested in Vite's success.
I'm sure some of you have heard of Svelte and SvelteKit, but a lot of you probably haven't, so I'm going to begin by explaining what those projects are. Svelte is a UI framework that's been around since 2016. Like other UI frameworks, its job is to let you write your application in a declarative, maintainable fashion using components. The big differentiator is that Svelte is first and foremost a compiler, which means that component authoring doesn't come with the same set of constraints that are associated with most frameworks. Because of that, we're able to write components in a very concise, expressive way while still resulting in incredibly efficient applications for our users. And people really love using Svelte. Among respondents to the most recent state of JavaScript survey, we have the highest satisfaction scores of any front-end framework, jointly with Solid, and we're the framework that the largest number of developers wants to learn.
If you're one of those people, it's very easy to get started using the instructions on svelte.dev. Just run this command, which will create a Vite project using Svelte, and you're off to the races.
But there's a catch. At some point as you're building your application, you're going to run into a whole bunch of questions like: how do I structure my source code? How do I add client-side routing? How do I add server-side rendering, which is really important for performance, resilience and search engine optimization? Given that we have server-side rendering, how do we add server-side routing, ideally in a way that doesn't mean duplicated work? How do I load data in order to render it, as well as getting data from the server to the client? How do I get data back from the client to the server? How do I handle environment variables, including secret ones, sensibly and securely? How do I prepare my application for deployment to a wide range of possible environments: static files, Node servers, serverless functions, edge functions, and so on?
Now a component framework like Svelte can't answer these questions, it's not its job. And it turns out neither can a tool like Vite because Vite, as we've talked about, is this infrastructural layer, and infrastructure almost by definition has to be relatively unopinionated so that it can support the widest range of possible use cases.
And this is where an application framework like SvelteKit comes in. SvelteKit answers all of those questions for you so that you can spend your time on the creative and unique parts of your app. We've been working on it for two years with a very energetic community helping us figure out the right answers to those questions and we're really happy with how it's shaping up. It is pre-release software in a release candidate phase, which means that we're almost feature complete and don't expect any more breaking changes, but it's not yet quite done. In spite of that, we're about to cross a hundred thousand downloads a week, which is a really cool milestone. Not so much as a vanity metric, but because it means that if you choose to use SvelteKit, you'll be in a club of people who are all building apps in a fairly similar way and can share knowledge and support one another.
Demo: Project Setup and File Structure
So let's take a look at this SvelteKit project. Very easy to create one, just do npm create svelte@latest and follow the prompts. We'll create a new app called viteconf-demo and add TypeScript and Prettier. I'm not going to bother with tests and stuff for now. Install dependencies with pnpm, because pnpm is cool, although you can of course use npm if you prefer slow things. Then we can pop the hood and take a look inside.
The thing you'll notice is our Vite config of course, because this is just a regular Vite project using the SvelteKit plugin, but we can add whatever else we need for our project.
All of our Svelte configuration goes in this svelte.config.js file. We have a preprocessor for TypeScript and the default adapter which will let us deploy to various platforms with zero manual configuration. This is also where we configure the plugins for Svelte, so I'm going to add the experimental inspector option.
Now if we start a development server, it'll show us the welcome page. We can check that JavaScript is working by fiddling with the counter, and if we want to accept the invitation to edit this page, we can bring up the inspector with command-shift, select the element we want to edit, and our editor opens right up to the right place. Obviously we're using hot module reloading, so we get that nice zippy feedback.
Let's take a look at this +page.svelte file. As the name suggests, this adds a page to the app, and because this file is in the root of our source routes directory, this is our root page. The + prefix is slightly unconventional, but it works really well to differentiate route files from everything else, which makes it really easy to organize our code, unlike frameworks where every file inside the routes directory is assumed to have some greater significance, so you have to put single-use files in a completely separate directory for no good reason. It also makes it very easy to navigate around the codebase. For example, if I open the go-to menu and type a few characters, I can immediately see all the pages in my app.
So back to the root page. If you haven't seen Svelte code before, hopefully this is fairly self-explanatory. It's a superset of HTML, so any valid HTML is also valid Svelte code. There are some things in here that you won't find in HTML: these JSX-like curly brace expressions, for example, or this child Counter component with a capital C. These are coming from the script element at the top of the page. You'll notice that we're importing .png files. This is a Vite feature that turns static assets like images into a hashed URL that can be cached forever, so our users only need to download them once. We're importing them from the $lib alias, which maps to the source lib folder. This is a great place to put things like images or components and modules that are used throughout the app.
At the bottom of the file we have styles for our page. You'll notice that we're using very terse selectors because these styles are scoped to this component, meaning we don't need to worry about selectors matching elements elsewhere on the page.
There's also this companion +page.ts file for fine-tuning the behavior of the page, which in this case contains a single line: export const prerender = true. We'll come back to that later on.
We have another route file in this directory: +layout.svelte. This contains the stuff that's used on every page, like the nav bar and the footer. We're also importing some global styles which aren't scoped to any individual component. Layouts in SvelteKit can of course be nested, so we don't have any nested layouts in this app.
If we navigate to the about page, you'll see that it's really instantaneous, and that's because even though SvelteKit server renders your pages like a traditional multi-page app framework, unlike a multi-page app framework you don't need to go back to the server and get a whole new page every time you navigate. Instead SvelteKit will just get the code and the data that it doesn't yet have. You don't need to get the head content and the nav bar and the footer because we already have that stuff. In fact, we don't even replace those elements. We only replace the bits that have actually changed. By contrast, if you do a full page reload, then not only do you need to download a bunch of unnecessary data on every navigation and replace elements that haven't changed, you also need to re-run any scripts you have on the page. And we all know that things like third-party analytics scripts, which are largely outside your control, can get a little bit chunky.
We have another trick up our sleeves to make navigation feel super snappy. This is our app.html file, which determines the overall shape of the response. You'll notice that on the body element we have a data-sveltekit-prefetch attribute. That means that every link inside that element will be prefetched as soon as your mouse comes to a rest over it, or on mobile as soon as you touch it, which gives us enough of a head start that navigation usually feels instantaneous, even if you have to load some data from the server.
And by the way, we're just using <a> elements for links instead of framework-specific components, which means that you get client-side navigation and prefetching even for links that come from a CMS or something like that.
So far we've seen that SvelteKit gives you a way to structure your source code that also configures your routing, both server and client side, and gives you server-side rendering out of the box. What about data?
For that, we need to look at the final page in the demo app, which is a Wordle clone called Sverdle. If you haven't played Wordle before, the goal is to guess a five-letter word. After each guess, the game tells you which letters are in the right place and which letters are right but in the wrong place. What's interesting about the original Wordle is that it's a purely client-side app, including all the answers, which means that it's really easy to reverse engineer it and cheat.
Now, I think that encouraging cheating threatens the very fabric of society, so in our implementation, all of the game logic lives on the server instead. And a nice side effect of that is that the browser doesn't need to download all 13,000 or so possible answers. Another really cool feature is that because the logic lives on the server, we don't actually need JavaScript at all. If it's disabled or fails to load for some reason, you can still play the game. We just use a tiny bit of client-side JavaScript to make the game smoother and more instantaneous when it is available.
Let's take a look at the source code. This time instead of +page.ts, we have +page.server.ts. Anytime you add .server to a file, you ensure that it will only run on the server.
This load function is responsible for getting data from the server to the page. In this case, it reconstructs the game state from the user's cookies or creates a new game, then returns an object with three properties: the guesses that have been made so far, the encoded answers that show which letters are correct, and if we lost the game, the revealed answer.
Below that, we have what are called actions, an update action for updating the game state in response to a single key press, an enter action for guessing a word, and a restart action that just deletes the cookie so you can start over with a new game.
You'll notice that this data handling stuff lives in a different file to our UI code. That's very deliberate. Some frameworks encourage you to put your server code and your UI code in the same file, but we think this is very misguided. Our experience has shown that when you do that, people get very confused about what code can run where, and it becomes frighteningly easy to do things like accidentally include sensitive code in your client-side bundles. Having this clear separation is really important.
So what's on the other side of the fence? Again, we have a +page.svelte file, but this time it accepts some data. And if we hover over that, we'll see that it's the return value from our load function. That information comes from this types file, which SvelteKit creates for you behind the scenes. After the data, we have some reactive statements and a couple of event handlers, and then the real fun starts.
This form element is what makes everything possible. It posts the data from these input elements to the enter action which updates the game state. By adding this use:enhance snippet here, we can submit the data and update the page without doing a full page reload, so it feels like everything is happening in the page, and we haven't had to write a single line of fetch code anywhere. This is all just HTML. We don't even have a capital-F Form component, we're just using the native HTML elements.
Demo: Progressive Enhancement and Security
Further down, we have the buttons that let us interact with the form. The first button submits the form if we have a word to guess. These next two buttons have a formaction attribute which tells the form to post to the update action instead of the enter action. Though if JavaScript is available, then we modify the game state client-side using this update event handler rather than making a trip to the server.
Now, some of you might be more used to using the fetch API to communicate with your server, but I think you'll find that when you start to do things in a more HTML-native fashion, things start to click together really nicely.
I just want to show you what happens if we were to accidentally import the word list into public-facing code. We add the import, and the page immediately breaks with an internal error. We can customize this error page, of course, we can create a +error.svelte page, either for specific routes or for the app as a whole. But in this case, what we really want to do is get rid of the offending code.
This also applies to environment variables. Suppose we don't want to have this cookie name hard-coded in our +page.server.ts. We can create a .env file and add an environment variable called COOKIE_NAME and then replace all the occurrences in our code. You'll notice that it auto-completes and automatically imports this env module. This variable isn't public, so just like with our word list, if you try to import it into public-facing code, it will cause an error. If we do want to make environment variables accessible to public code, we just add a PUBLIC_ prefix like this, and now we're able to console.log it inside our page.
Demo: Deployment and Prerendering
So we're doing pretty well on our bullet point list. All we need to do now is deploy our app.
By default, apps include an adapter that will automatically configure your app for certain popular platforms like Vercel and Netlify, and we plan to add to that list over time. For now, let's deploy our app to Vercel. That was pretty easy. But there is one problem here. By default, at least today, Vercel apps are deployed as single-region serverless functions. In our case, that's not ideal because we're not loading data from a central server. It would be better if we could run our game logic at the edge so that you get very fast responses wherever you are in the world.
To do that, I'm going to install Svelte's adapter-vercel, swap out the auto adapter, and add the edge: true configuration. Now when I redeploy to production, my app is going to be served by Edge functions instead of a Lambda, making it much faster.
Of course, for the home and about pages, we don't actually need to run any code at all because they're always going to be the same. And that's where this export const prerender comes in. When a page has that configuration, SvelteKit will generate HTML at build time, and it won't bother including code for it in your server functions, because no code is faster than running no code.
So that last part was a bit of an ad for Vercel, but SvelteKit can run anywhere JavaScript runs, so you can use it with your stack of choice, including to static hosts like GitHub Pages if your app is suitable.
Conclusion and Further Learning
Okay, that was a super brief intro to SvelteKit. We've barely scratched the surface of what the framework can do. We haven't talked about how it protects your users from cross-site request forgery attacks or how it enables you to set up robust content security policies or how it helps you deal with new versions of your app while your users are still using the old one. But hopefully, it's given you a bit of a sense of what it's like to work with. If you're interested in learning more, come see us at github.com/sveltejs/kit or on Discord at svelte.dev/chat. Thank you for watching and I'll see you around.