7

A lot of developers have recently announced that they have migrated their website to Astro . This is usually accompanied by a screenshot of a near-perfect Lighthouse score and a series of rocket emojis.

Like most people, I find the endless stream of new frameworks tiresome. But I did some research on Astro and thought it was really worth a try.

In this article, I'll show you how to use Astro to build a Vue-based application, and we'll see how its unique architecture can lead to better performance than a single-page application (SPA).

SPA Architecture Review

Before we can see Astro in action, we need to understand its architecture. To that end, let's first remind ourselves of the pros and cons of the single-page application architecture.

SPA abstracts all functionality and content of a website into JavaScript components. This is great because it makes website development easy.

The downside of this approach is when the site goes into production. All these JavaScript components are bundled together into one big application. Due to the large size, the browser may be slow to download and run.

Of course, you can optimize this bundle by code splitting. However, there are still some upfront costs the browser has to pay just to launch the website.

<!-- 典型的 SPA 页面 -->
<head>
  <script src="/app.js"></script>
</head>
<body>
<!-- 在 app.js 加载之前,此页面没有有意义的内容 -->
<div id="app"></div>
</body>

Islands architecture

The Islands architecture, the architecture used by Astro, also uses components. However, unlike single-page applications, these components are not bundled into a single JavaScript package.

Instead, each component is treated as an independent mini-application that exists in isolation from all other components.

For example, if your page had a JavaScript-based navigation bar, that would be a mini-app. If it also has a JavaScript-driven image carousel, that's another mini-app. etc.

However, if these components are not bundled, how can they be included in the project? I will explain this in the next section.

<!-- Islands architecture -->
<body>
<MyNavBar /> <!-- navbar mini app -->
<main>
  <MyCarousel /> <!-- carousel mini app -->
  <div class="content">
    <!-- more page content... -->
  </div>
</main>
</body>

server-rendered components

Astro is primarily a static site generator. It works with most UI libraries that support server-rendered components, including Vue, Svelte, Preact, React, and Lit.

So when Astro builds your application, every JavaScript component is loaded on the server side and the content is a "snapshot". This snapshot is added to the static page.

Server rendering isn't an Astro-specific feature, but in SPAs it's an optional feature, and in Astro, it's a vital feature, as we'll see below.

<!-- 开发内容 -->
<body>
<MyForm /> <!-- JS component -->
</body>

<!-- 快照内容 -->
<body>
<form> <!-- Server rendered JS component -->
  <input type="text" >
  <!-- ... -->
</form>
</body>

Progressive hydration

That's the magic of Astro - through a combination of islands architecture, server-rendered components, and progressive hydration.

Since our pages are divided into server-rendered mini-apps, the interactivity layer (JS) can be loaded independently and only when needed.

For example, you might have an interactive form. This form is below the page, outside the viewport.

The form is server rendered, so we see it on the page. However, expensive JavaScript doesn't need to be loaded until the user scrolls it into view.

That's what "progressive hydration" means in Astro - we only load JavaScript when we need it.

Build a Vue + Astro project

Now that the theory is out of the way, let's see it in action!

To start creating the Astro project, we will first create a directory:

$ mkdir vue-astro

Then run the Astro installation wizard:

$ npm init astro

The installation wizard will allow us to select "Vue" as our framework of choice. This will create a boilerplate project with Vue components.

Astro Components

Astro pages are stored in the src/pages directory. In the default installation, we see a file index.astro as shown below.

src/pages/index.astro

---
import VueCounter from '../components/VueCounter.vue';
let title = 'My Astro Site';
---
<html lang="en">
<head>
  <!-- ... -->
  <title>{title}</title> 
</head>
<body>
  <main>
    <!-- ... -->
    <VueCounter client:visible />
  </main>
</body>
</html>

Astro has a single-file component style, similar to Vue, with some important differences.

First, at the top of the file, we see what appears to be the front-end content, which is delineated --- This is JavaScript running on the server side. This will not be sent to the client.

Here we can see two important things: First, we are importing a Vue component (you can import components from any supported framework). Also, we are setting a value: title .

All variables declared here are available in the template. You'll notice that title in the template in a JSX-like syntax.

src/pages/index.astro

---
...
let title = 'My Astro Site';
---
<html lang="en">
<head>
  <!-- ... -->
  <title>{title}</title> 
</head>
<!-- ... -->

Next, notice the components declared in the template.

By default, components are not interactive on the client side, but are server-rendered by Astro.

If we want the component to be interactive, i.e. load JavaScript, we need to give it an instruction to tell the client when to load it.

In this case, the client:visible VueCounter interactive when the component is visible in the page.

If this happens, Astro will request the component's JS from the server and hydrate it.

---
import VueCounter from '../components/VueCounter.vue';
...
---
<html lang="en">
<head><!-- ... --></head>
<body>
  <main>
    <!-- ... -->
    <VueCounter client:visible />
  </main>
</body>
</html>

Load Astro

Now let's run Astro's development server to view our project.

npm run dev

In the source code of the page, you'll see that there isn't any JavaScript bundled in the documentation! We do see server-rendered Vue components, though.

We also see that Astro adds a script to the bottom of the document body. Here, it loads a module to hydrate Vue components.

This module will download Vue components and dependencies (Vue framework) without rendering blocking.

index.html

<!-- Page source -->
<body>
<!-- server rendered component -->
<div id="vue" class="counter">
  <button>-</button>
  <pre>0</pre>
  <button>+</button>
</div>

<!-- 添加的代码片段以水合 Vue 组件 --> 
<script type="module">
import setup from '/_astro_frontend/hydrate/visible.js';
// ...
</script>

Why Vue + Astro might be better than Vue SPA

To see why Astro can beat Single Page Apps in terms of UX, let's do a simplified breakdown of what happens when a website loads.

  1. index.html is already loaded. It doesn't have JS bundled, but it includes your server-rendered components, so users can already see your website content -- just not yet interacting with it.
  2. Any JS required by the component will now be downloaded asynchronously as a series of independent scripts.
  3. After downloading these scripts, they will be parsed and run. It is now possible to interact.

Now let's imagine that we rebuilt this website as a one-page application. How will it load now?

  1. index.html is loaded. Since the page contains no content, the user cannot see anything. The browser will start downloading the bundler.
  2. After downloading the JS package, the browser parses it. The user still doesn't see anything.
  3. Once the JS bundle is parsed and run, the page content is generated. Users can now view and interact with the application.

put it simply: Astro websites provide visible content almost instantly, unlike SPAs that need to download and run a JS package first.

(Astro app will also provide interactivity a little earlier, since it may not need to download as much JS as there is no SPA shell, router, etc.)

final thoughts

Astro's architecture is probably a better choice than a single-page application because it makes content visible without JavaScript, and only loads JS when needed.

In theory, a single page application could achieve a similar effect with a combination of prerendering and code splitting. The difference is that Astro sites are optimized this way by default, since you need to opt-in for interactivity and JS.

Of course, not every application will benefit from this architecture, as SPAs are better suited for certain types of applications, such as highly dynamic and interactive applications. So we don't expect the SPA architecture to disappear.


杭州程序员张张
11.8k 声望6.7k 粉丝

Web/Flutter/独立开发者/铲屎官