Back to Blog
Frontends are hard

Frontends are hard

Frontends are hard

·Unknown

像 Next.js、Svelte、Remix、Astro 等现代前端框架部署起来很困难。Netlify和Vercel等服务商专门为此构建了定制的基础设施。

在这篇文章中,我们将探讨为什么前端部署变得如此困难,以及为什么像 AWS、GCP 或 Azure 这样的大型云提供商的****支持非常糟糕

我们还会探讨最近的一些变化如何使 SST 能够更好地支持它们。


背景

回到 2010 年代,前端都是单页应用。托管起来非常简单,只需将其上传到 S3 存储桶,然后将 URL 分享给用户即可。如果想更高级一些,还可以添加 CDN。

你可以在任何云服务提供商上实现这一点,比如 AWS 或 GCP,而且这是你整个技术栈中最简单的部分

现代前端现在需要结合使用 S3 存储桶、无服务器函数、数据库、CDN、边缘函数、边缘数据存储等基础设施。

此外,还有十几个与之竞争的框架。

这非常复杂,以至于即使是 AWS 和 GCP 的AmplifyFirebase等服务对它们的支持也非常差。它们只支持少数几个框架,不支持它们的最新版本,而且也没有涵盖它们的所有功能。

因此,大多数人会选择使用像 Netlify 或 Vercel 这样的二级云服务提供商。这些服务在主流云服务提供商的基础上构建了自己的定制基础设施。它们正是利用这种方式实现了令人瞩目的增长。据报道,这两家公司的年度经常性收入 (ARR) 都达到了 1 亿美元左右。

但我们为什么需要专用服务?部署前端到底有多难?为什么连大型云服务提供商都无法满足需求?


为什么这么难?

前端部署和托管困难的主要原因有以下几点:

  1. 复杂的基础设施

    单页应用只包含静态文件。但现代前端已经发展到支持服务器端渲染、API路由、图像优化、边缘计算支持、中间件等等。它们现在更接近全栈应用。

    现代前端越来越接近全栈应用程序。

    这需要结合使用 S3 存储桶、无服务器函数、数据库、CDN、边缘函数、边缘 KV 存储等基础设施。

    对于托管现代前端而言,没有一种基础设施可以适用于所有情况。

  2. 十几种不同的框架

    从 Next.js 到 Astro、SvelteKit、Remix、SolidStart,再到最近推出的 TanStack Start 或 React Router v7 等框架模式的框架,至少有十几个相互竞争的框架,它们各自拥有不同的功能或侧重点。

    这就导致 AWS 和 GCP 需要自行选择支持哪些服务。

  3. 更新速度加快

    All these frameworks are also constantly being updated.

    The big cloud providers are slow to release updates on their end. Meaning that the versions they support are almost always out of date.

Caveats

You could self-host most of these frameworks in a container, or self-host as a single-page app. But these modes of deployment have their limitations and are clearly sub-optimal compared to using a dedicated service.


Open source for the win

When we had first started building SST, deploying frontends was a small part of what we did. But as we grew, we started to get more requests for better frontend support.

Self-hosting frontends is a perfect fit for an OSS project.

We looked at the above problems; not having a one-size-fits-all solution and a long tail of frameworks and features. And realized that this was a perfect fit for an OSS project.

So here’s what we did:

  1. Break down the problem

    Deploying a frontend is a combination of generating a build output through something called an adapter. And then using that to deploy the infrastructure.

    While the infrastructure is specific to the provider, the adapter could potentially be shared.

    So when we wanted to support frontends in SST, we decided to separate the two steps, and open source the adapters.

    This is led us to start the OpenNext project.

    Now providers like Cloudflare, and even Netlify and Vercel, can contribute to the project and help keep it up to date.

  2. Open source everything

    Aside from adapters, even the infrastructure SST uses to deploy frontends is open source. You can just look at our repo to see how we deploy them.

    This allows people to contribute and makes it so that SST supports a large number of frameworks, covers all their features, and remains up to date with new releases.

  3. Work closely with the framework authors

    Since everything is open source, it also makes it easier for us to collaborate with the framework authors directly.

This made SST the best available option for people that want to deploy their frontend to their infrastructure.


Good or good enough?

The infrastructure that SST creates when you self-host your frontend is a little different from what Netlify or Vercel creates.

It stems from the fact that Netlify and Vercel are multi-tenant and can share some bits of infrastructure across all their users.

Whereas SST creates a completely isolated setup for your frontend where nothing is shared between your sites and you are of course isolated from other SST users.

The downside of our approach is that we recreate all the infrastructure, like the CDN distribution, for each of your sites. This means it can take 15-20 mins to create one of these. So if you have multiple frontends or if you were creating preview environments, your deployments will be slower.

And somewhat related, SST didn’t really support deploying your frontend to multiple regions.

We had been looking for ways to improve our setup. And now we can, thanks to this.


A new approach

A seemingly trivial pre:Invent announcement made CloudFront a lot more programmable. This paved the way for us to support setups that are far more flexible than Netlify or Vercel.

The key change here is that you can now use CloudFront Functions in tandem with a CloudFront KeyValueStore to modify the origin of requests. This lets you create custom policies for how traffic is routed from the CDN to your application.

In the past, you’d have to use a Lambda@Edge function and a DynamoDB table which is both a lot more expensive and a lot slower.


Introducing Router

We used this change to build the Router component. This component lets you use a single CloudFront distribution for your entire app.

sst.config.ts

const router = new sst.aws.Router("MyRouter", {  domain: {    name: "example.com",    aliases: ["*.example.com"]  }});

With it:

  1. You can set up routes to your frontends.

    sst.config.ts

    const web = new sst.aws.Nextjs("MyWeb", {  router: {    instance: router  }});

    Or functions.

    sst.config.ts

    const api = new sst.aws.Function("MyApi", {  url: true,  router: {    instance: router,    path: "/api"  }});

    Or S3 buckets.

    sst.config.ts

    const bucket = new sst.aws.Bucket("MyBucket", {  access: "cloudfront"});router.routeBucket("/files", bucket);

    Or any URL.

    sst.config.ts

    router.route("/external", "https://some-external-service.com");
  2. You can configure it to serve a subdomain.

    sst.config.ts

    new sst.aws.Nextjs("MyWeb", {  router: {    instance: router,    domain: "docs.example.com"  }});

    Or a path.

    sst.config.ts

    new sst.aws.Nextjs("MyWeb", {  router: {    instance: router,    path: "/docs"  }});

    For this, you’ll also need to set the basePath in your next.config.js.

  3. You can share a router across stages. So your preview environments can deployed almost instantly.

    sst.config.ts

    const router = $app.stage === "production"  ? new sst.aws.Router("MyRouter", {     domain: {       name: "example.com",       aliases: ["*.example.com"]     }   })  : sst.aws.Router.get("MyRouter", "A2WQRGCYGTFB7Z");
  4. You can also deploy your frontends to multiple regions and it’ll route requests to the server function that’s nearest to your user.

    sst.config.ts

    new sst.aws.Nextjs("MyWeb", {  regions: ["us-east-1", "eu-west-1"],  router: {    instance: router  }});

How it works

The Router uses a CloudFront KeyValueStore to store the routing data and a CloudFront Function to route the request. As routes are added, the store is updated.

So when a request comes in, it does a lookup in the store and dynamically sets the origin based on the routing data. For frontends, that have their server functions deployed to multiple regions, it routes to the closest region based on the user’s location.


Why this matters

Taking a step back for a second, a dedicated service like Netlify or Vercel is still likely to give you a better out-of-the-box experience.

Where SST shines, is when you start to grow. You start adding multiple frontends. You want serve your API from the same domain. Or you want your preview environments to include more than just your frontend.

This new setup allows you to do all of that. This is an example of one of SST’s design principles. You can start with the simplest setup, without using a Router. Then grow to adding it. And eventually customize it. All through code.


Learn more

You can learn more about this new setup:


Final thoughts

There are also some broader questions like:

  • Why are there so many frameworks?
  • Why do these frameworks change all the time?
  • 前端真的需要这么复杂吗?

这些都是值得深入探讨的好问题。我们可能会在以后的文章中探讨这些问题。如果您有什么想法,也欢迎与我们分享。