Blog

Svelte Kit, the first ‘serverless-first’ framework?

"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 renderfunction 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 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.

← All blog posts

Also in love with the web?

For us, that’s about technology and user experience. Fast, available for all, enjoyable to use. And fun to build. This is how our team bands together, adhering to the same values, to make sure we achieve a solid result for clients both large and small. Does that fit you?

Join our team