"The whole serverless thing is a pretty good trend, and Svelte is now a serverless-first framework"
That quote is from Rich Harris, creator of Svelte. A bold statement, but is the newly introduced Svelte Kit really “serverless-first”? And what does that even mean? And if so, how well can this new Svelte seamlessly integrate with serverless platforms like Netlify and Vercel?
Big fat disclaimer: Svelte Kit is in early development. The source code isn't public yet. To get a sneak peek I installed the "next" version (@1.0.0-next.0
) of Svelte and its new serverless adapters:
npm init svelte@next
npm install -D @sveltejs/adapter-node
npm install -D @sveltejs/adapter-static
npm install -D @sveltejs/adapter-netlify
npm install -D @sveltejs/adapter-vercel
My findings are based on sifting through the compiled code with the help of Sokra's source code visualiser.
Serverless-first using adapters
Svelte’s answer to become serverless-first in a fragmented landscape of serverless platforms is its introduction of so-called serverless adapters. While all serverless providers like Netlify and Vercel offer similar functionality — static hosting, cloud functions and configurable redirects — they each have their own vendor-specific implementation.
To ensure Svelte runs seamlessly on all of them, Svelte will provide an adapter for each. Each adapter does three things:
- copy Svelte’s compiled client-side JS bundles and other static files to the serverless hosting directory.
- copy Svelte’s server-side JS bundles and a platform specific render function to the serverless functions directory.
- create the redirects configuration with a catch-all route to the serverless render function.
The adapter takes platform specific configuration (files) into account. Here’s the gist taken from the Svelte adapter for Netlify (@1.0.0-next.0
):
// get netlify configuration, defined by user in netlify.toml:
const netlify_config = toml.parse(fs.readFileSync('netlify.toml', 'utf-8'));
const publish = path.resolve(netlify_config.build.publish);
const functions = path.resolve(netlify_config.build.functions);
// copy static and client files files to static hosting directory:
builder.copy_static_files(publish);
builder.copy_client_files(publish);
// copy the renderer and server files to cloud functions directory:
fs.copyFileSync(path.resolve(__dirname, 'files/render.js'), `${functions}/render/index.js`);
builder.copy_server_files(`${functions}/render`);
// create _redirects file with catch all route to serverless render function:
fs.writeFileSync(`${publish}/_redirects`, '/* /.netlify/functions/render 200');
While I believe adapters are the way forward towards true serverless integration of JavaScript frameworks, I don’t think Svelte is truly serverless-first yet. Here’s why:
Serverless-first requires server logic
Part of the fun of serverless rendering over static site generation is being able to respond dynamically to incoming requests. For instance if a request has query parameters or a payload, you'd want to use that request data in your Svelte app. As far as I can tell the adapters and Svelte app don't expose this data to the routes yet. Here’s the serverless render
function from the Netlify adapter (@1.0.0-next.0
):
exports.handler = async (event) => {
const { path, httpMethod, headers, queryStringParameters
// body, // TODO pass this to renderer
// isBase64Encoded // TODO is this useful?
} = event;
const query = new url.URLSearchParams();
// [...] code to append queryStringParameters to query object
const rendered = await app.render({
method: httpMethod, headers, path, query, host: null // TODO
});
if (rendered) {
return {
isBase64Encoded: false,
statusCode: rendered.status,
headers: rendered.headers,
body: rendered.body
};
}
return { statusCode: 404, body: 'Not found' };
};
Judging by the TODO
comments, this is clearly a work-in-progress. Svelte already passes some request parameters to the Svelte app render function. But as you can see it doesn’t pass the payload body
and host
yet. For meaningful serverless apps, Svelte will need to pass these as well as the missing second argument of this lambda handler (async (event, context)
) and also pass these onto the routes. In addition Svelte will need to normalise the parameters (and context?) best it can between the different serverless providers for a unified developer experience.
Once Svelte exposes the request and context, developers will want to handle these in their app. A good reference of what this could look like is the recently added [getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering)
in Next.js. Here’s an example of server-side only logic leveraging the request context:
// example of server-side only logic in a Next.js route:
const { verify, decode } = require('jws')
export async function getServerSideProps(context) {
const token = context.req.headers.auth
if (!token || !verify(token, 'HS256', JWT_SECRET)) {
return {
redirect: { statusCode: 401, destination: '/login/' }
}
}
const { userId } = decode(token).payload.app_metadata
const user = await fetchUser({ userId })
return {
// will be passed to the page component as props
props: { user },
}
}
export default function Page({ user }) {
// render page with user data
}
Sapper - the Svelte version of Next.js - has preload
which is somewhat similar, but runs both on server- and client-side; and doesn’t expose as much of the context. Whatever it will look like, the new Svelte will need to introduce some way for developers to add their own server-side logic to really be serverless-first.
Serverless-first in development mode
Serverless providers including Netlify and Vercel worked hard to offer tooling to make local development as close to production as possible. Both netlify dev
and vercel dev
offer full support for serverless functions. But while Svelte Kit offers adapters for these serverless providers, they come in the shape of a post-build step:
npm run build # svelte-kit build
npm run adapt # svelte-kit adapt
This feels like an afterthought rather than a serverless-first approach. And this means I can’t test my serverless Svelte app in development, but always need to create a (production) build.
Svelte could achieve serverless-first in development mode by creating the redirects configuration during svelte-kit dev
, connecting the serverless render function to unoptimised server bundles and putting the local server of the selected serverless provider in front of Svelte’s own local server. This way, I can also add my own non-Svelte serverless functions to the project and run them in the same development setup. Svelte already made the client-side development experience instant by adopting Snowpack, now it’s time to do the same for serverless development.
A serverless-first future
Seeing as Svelte Kit is in early stages and the Svelte team has already proven they can make a serverless framework with Sapper, I’m optimistic the next Svelte will be truly serverless-first. I hope the serverless functions will be extendible and work in development mode in a future version of Svelte.
Top comments (2)
Making PWA with Sapper and the service-worker was quite easy.
Any idea if there will be a PWA adaptor? or a way of adding a service-worker to a a Svelte Kit build? I've been searching everywhere for any information on this.
Thanks!
Hi Joshua. Good question. I didn't spot anything in the early release of Svelte Kit yet. You can add your own service worker and there's some templates and tutorials out there to add them to your Svelte app, but of course that's not as easy as with Sapper. I do remember Sapper having service worker support from early on. So I imagine Svelte Kit will have the same. Though maybe it will be an optional package just like the adapters.