I recently started using Astro to rebuild side projects that were originally built with WordPress, Go, Rails, and Hugo. I picked Astro because it had a React-like DX with good language server support, and because it was compatible with serverless hosting platforms that had a generous free tier (Cloudflare, AWS Lambda, etc).
I didn't know much about Astro when I started using it. Now that I've migrated multiple sites, I wanted to share what I liked and disliked about the framework for anyone else considering using it.
At its core, Astro is a static site generator with the ability to produce dynamic server-rendered pages when needed. Astro is a great fit for blogs or small marketing sites with limited interactivity. The framework provides most of the DX from Next.js without the React.js overhead.
Let's be honest: good language server support and code formatting are severely lacking in traditional server-rendered templating languages. Go templates, Jinja, ERB, and EJS are painfully behind with tooling when compared to their React/Vue/Svelte counterparts. Most server-rendered templating languages have no way of knowing what variables are in scope or what their types are.
With Astro, all of these features are one VS Code extension away.
Inside of Astro templates, you set your data at the top of the template inside of a "code fence" that either runs at build time or when responding to a request on the server. Here's what that looks like in practice:
--- import Layout from "../layouts/Layout.astro"; import { getPosts } from "../data/posts"; const posts: { id, title }[] = await getPosts(); --- <Layout pageTitle="Posts"> <h1>Posts</h1> {post.length > 0 ? ( <ul> {posts.map((post) => ( <li> <a href={`/posts/${post.id}`}> {post.title} </a> </li> )} </ul> ) : null} </Layout>
Because all of the data for the template is loaded in the "code fence" above the template, the language server can provide auto-completion for the properties of any object defined within the scope. It will also indicate when you are trying to use a variable that doesn't exist.
One of my biggest gripes with traditional templating languages like Go templates, Jinja, and EJS is that they don't have "components" that can accept children. Most of my websites have a constrained-width "container" UI element of some kind, which ensures that content doesn't fly out to the end of the screen on ultra-wide monitors. If you have a .container class that you manually add to
elements, then this usually works fine. However, if you're using a utility CSS framework like Tailwind then you may find yourself adding the following code to every single page template:<div > <p>When you eventually need to change these classes, it's an error-prone pain to update each file manually. But if your templating language doesn't have components that can accept children, it's almost inevitable. </p> <p>Unlike those traditional templating languages, Astro templates can be used as components that accept children using a <slot /> tag. A long string of utility classes could be extracted into a reusable Astro component:<br> <pre class="brush:php;toolbar:false"><div > <p>The Astro component could then be consumed from another Astro file.<br> </p> <pre class="brush:php;toolbar:false">--- import Container from "../components/Container.astro"; --- <Container> <h1>Hello, world!</h1> </Container>
Astro files aren't limited to a single slot: they can have multiple.
My favorite feature of Astro components is that they can accept props within the code fence. Here's an example:
--- import Layout from "../layouts/Layout.astro"; import { getPosts } from "../data/posts"; const posts: { id, title }[] = await getPosts(); --- <Layout pageTitle="Posts"> <h1>Posts</h1> {post.length > 0 ? ( <ul> {posts.map((post) => ( <li> <a href={`/posts/${post.id}`}> {post.title} </a> </li> )} </ul> ) : null} </Layout>
The component can then accept props when used within another file.
<div > <p>When you eventually need to change these classes, it's an error-prone pain to update each file manually. But if your templating language doesn't have components that can accept children, it's almost inevitable. </p> <p>Unlike those traditional templating languages, Astro templates can be used as components that accept children using a <slot /> tag. A long string of utility classes could be extracted into a reusable Astro component:<br> <pre class="brush:php;toolbar:false"><div > <p>The Astro component could then be consumed from another Astro file.<br> </p> <pre class="brush:php;toolbar:false">--- import Container from "../components/Container.astro"; --- <Container> <h1>Hello, world!</h1> </Container>
I've built my own server-side integrations with Vite before. If you're trying to get something online quickly, this is the kind of commodity feature that you want to avoid building yourself. With Astro, it's built-in.
If you want to add a custom script to an Astro page or component, all you have to do is drop a script tag on the page.
--- type Props = { pageTitle: string; pageDescription: string }; const { pageTitle, pageDescription } = Astro.props; --- <!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>{pageTitle}</title> <meta name="description" content={pageDescription} /> </head> <body> <slot /> </body> </html>
Astro will automatically compile TS and JS files as a part of a site's build process. You can also write scripts that use imports from node_modules inside of a script tag within an Astro component, and Astro will compile that during build time and extract it to its own file.
--- import Layout from "../layouts/Layout.astro"; --- <Layout pageTitle="Tyler's Blog" pageDescription="I don't really post on here." > <h1>Tyler's blog</h1> <p>Sorry, there's no content yet...</p> </Layout>
You can include CSS or Scss styles in an Astro file by importing them within the code fence.
<div> <h1>This is my page</h1> <script src="../assets/script.ts"></script> </div>
Astro also provides the ability to do scoped styles by using a style tag in an Astro file. This feature may be familiar to Vue users.
Given the following Astro file:
<script> // This will also compile down to a JavaScript file at build time. import axios, { type AxiosRequestConfig, type AxiosResponse } from "axios"; const response = await axios .get<AxiosRequestConfig, AxiosResponse<{foo: string}>>("/some-endpoint"); console.log(response.data.foo); </script>
Easy, right?
Actions provide a type-safe way of running backend functions. They provide validation and can handle both JSON and form data. They're easily one of Astro's killer features: all of this would need to be hand-wired in a Next.js app in a bespoke way. They require a little more code than can fit neatly into an example, but they're pretty elegant to use. I'd recommend reading the actions docs page.
There are an endless number of Twitter devs that say React is "fast enough." For a lot of things it's not.
I use Rasbperry Pi 4s for little projects, and you can feel the runtime cost of React. I'm sure it's the same for inexpensive Android phones, except in that case the JS overhead will also drain the battery.
If the only interactivity that my site needs is toggling a nav menu, I'd rather wire that up myself. I'll happily reach for React when I need it, but for so many projects I don't actually need it.
The things that I dislike about Astro are not unique to the framework: they are ideas borrowed from other tools in the JavaScript ecosystem.
Because Astro employs file-based routing, half of the files in an Astro project end up named index.(astro|js|ts) or [id].(astro|js|ts). File-based routing is an obnoxious pattern that swept the frontend world by storm after Next.js implemented it, and it comes with many drawbacks:
I'll admit: file-based routing feels great when you're making a site with under 10 pages. But as a site grows it adds friction, and you fight the feature more than you benefit from it.
In the JavaScript ecosystem, Remix stands apart by offering a version of file-based routing that flattens all routes into a single directory, and allows a user to opt out of file-based routing entirely with manual route configuration.
File-based routing is my biggest complaint about Astro, but it's a difficult feature to escape. It is implemented in Next.js, Nuxt.js, SvelteKit, and others. What's even stranger is that these frameworks are largely unopinionated about the filenames for other parts of the application. In start contrast to Ruby on Rails, most JavaScript frameworks give you a great degree of freedom in file names and project structure–except for routing. It's a special case, and special cases add complexity to software.
A JavaScript language feature that I really like is the ability to define multiple variables, functions, and classes in a single file. This makes it easy to colocate related functionality without having to prematurely extract it to other files because of language-level constraints.
Much like Vue's single-file components, Astro files allow defining one component per file. This feels tedious to me, but Astro provides a workaround.
Astro can embed pre-rendered React, Vue, Svelte, Solid, and Preact components directly into its templates without loading any client-side JavaScript. Preact components pair reasonably well with Astro because Preact JSX is much closer to HTML than React JSX. It does become awkward managing both Astro and Preact components in the same project though, and once I begin using Preact components I find myself moving most of the UI out of Astro files and into Preact.
If you're an avid user of Next.js, Nuxt, or SvelteKit and you are happy with your framework, you might not get much out of Astro. However, if you want to ship less JavaScript bloat to your users while retaining the DX of something like Next.js, then Astro might be for you.
Astro is geared towards content-driven websites, and provides markdown support out-of-the-box. Because of its focus on content, it is an ideal developer blogging platform to replace a WordPress or Hugo site. However, it's capable of much more than just content sites through features like Actions.
Despite my strong distaste for file-based routing, my biggest concern with adopting Astro is the potential for breaking changes that would force me to rebuild my sites. JavaScript tools are much more aggressive with breaking changes than tools you find in other language ecosystems. Because I'm so new to Astro, I don't know how much changes from one major version to the next. Even with this concern, I plan to move 5-to-6 of my sites from other platforms to Astro so I can take advantage of its top-notch DX and host the sites inexpensively.
The above is the detailed content of First impressions of Astro: what I liked and disliked. For more information, please follow other related articles on the PHP Chinese website!