π What is Astro?
Before getting started with Astro, the pertinent question is what on Earth is Astro? To describe SvelteKit you can say it's NextJS but for Svelte and a lot of people instantly know what SvelteKit does. Astro is a novel concept and so takes a little more explaining. Let's talk about what Astro lets you do. Astro lets you build fast apps with minimal JavaScript writing your code in HTML and vanilla JavaScript, React, Svelte or any mixture of those or a number of other languages. It is already clear Astro is quite different to NextJS, SvelteKit or Gatsby. What is does have in common with Gatsby is static site generation, though that is where the similarities end. Astro has its own Go compiler and uses vite tooling under the hood. This allows for a modern and more importantly fast developer experience.
As well as partial hydration, Astro offers an islands architecture. Don't worry if you don't yet know exactly what those terms mean, that's what we will look into next. In short Astro makes your app pages load faster. You get control of how your components load. You can instruct the browser to load a video part way down the page only when visible. Or the browser can work on your analytics code only when the main thread is idle (so we can optimise user experience) and you can even set certain components only to load on mobile or desktop. Now you see how different Astro is to other tools, let's take a deeper dive into the terms we introduced and how you can start using Astro.
π§ What is Partial Hydration?
Although partial hydration sounds like the marketing hype you might find on a new gym training supplement, it is actually a smart and effective idea. Essentially you send your page from the server to the client initially as pure HTML. By default Astro ships zero JavaScript. This is the secret to Astro's speed. From your JavaScript 101 course though, you will remember it is the JavaScript which makes you page interactive. That's where hydration comes in; hydration is the process of loading the JavaScript on your site page.
What if your site does not need to be interactive? You might have an FAQ page which just contains questions and answers with no forms, video or anything interactive which needs JavaScript. That's fine, we serve our page as pure HTML and never need to hydrate it. Astro is great for this kind of content. What if you wanted to add a comments section at the bottom of the page? Now you need JavaScript, but in fact you only need it once the user scrolls down to the bottom of the page. Astro can hydrate that comment form at the optimum time, again allowing your page to load quickly. Astro only hydrates the parts of the page which need hydration. This is partial hydration.
SEO Benefits
As well as user experience benefits, Astro has Search Engine Optimisation (SEO) advantages. With full hydration, essentially nothing is interactive until everything is interactive and the server ends up sending a larger bundle to the browser and rehydration can heavily delay time-to-interactive. This can impact first input delay, one of Google's Core Web vitals statistics. It is vastly more expensive to download and run JavaScript than simple HTML. Limiting JavaScript to the pages or parts of pages that need it should mean search engines like Google can crawl more content from your site in the time window allotted to you.
π What is an Islands Architecture?
Islands architecture relates to the partial hydration technique we just looked at. Astro's philosophy is to create Islands of Interactivity which the browser can hydrate independently. Treating each interactive element as independent allows you to optimise how it loads. For Core Web Vitals you will want a banner image at the top of the page to load instantly. You could go for pure HTML, but showing a low resolution placeholder while you wait for the responsive image to load reduces cumulative layout shift. In summary typically you want your banner image to load instantly. That is not the same for an image or video half way down the page. We can load these as they come into view. Remember with partial hydration we ship just the JavaScript we need.
Another island of interactivity might be an image carousel. What if you have different carousel components for mobile and desktop? With islands architecture you can load the specific one you need and forget the other. What about the chat bot that helps users with potential issues? Why not wait until the main thread is idle and then load it? All of these use cases result in your server shipping less code and the browser loading less code at a time, leading to increased efficiency.
π Why use Astro?
- it is fast β you get better user experience and expect a SEO boost as a bonus,
- you can use a language you already know, React, Svelte, Vue, Markdown or plain HTML,
- you can mix components from different languages, letting you gradually pick up Svelte.
Astro however, is not a one size fits all solution. It excels at building static sites, though will not be ideal when you really need a server side rendered app. As an example, let's say you were rebuilding Twitter. A static site does not fit this use case since you cannot fully customise for each user with a static model. Don't, however, interpret this to mean that you cannot have JavaScript or interactivity on a static site. You can still have a contact form or interactive feedback elements, the difference is that it becomes harder to customise them completely for a logged in user.
𧱠How do you Create a Minimal Astro App?
Blasting off on your new Astro project is as simple as typing a few commands to fire up the CLI and selecting Minimal when prompted.
mkdir my-new-project && cd $_
pnpm init astro
pnpm install
pnpm run dev
During this setup process you can alternatively choose from a list of Generic, Blog or a few other project types for your launchpad. By default Astro will start up on TCP port 3000
but don't worry if you already have something running there, it should be able automatically to find another available post. The CLI will tell you which port it settles for:
17:25 [astro] Port 3000 in use. Trying a new oneβ¦
17:25 [astro] Server started 63ms
17:25 [astro] Local: http://localhost:3001/
π§ Getting Started with Astro: What's Inside?
.
.
βββ README.md
βββ astro.config.mjs
βββ package.json
βββ pnpm-lock.yaml
βββ public
βΒ Β βββ favicon.ico
βΒ Β βββ robots.txt
βββ sandbox.config.json
βββ src
βββ pages
βββ index.astro
The
README.md
file contains some tips on getting going, eventually you will replace it with your project's own documentation.astro.config.mjs
is the main Astro config file. To use React or Svelte components in our project, we will need to install the React renderer (@astrojs/renderer-react
) or Svelte renderer (@astrojs/renderer-svelte
) and include whichever (or both) in the renderers array of this config file:
// Full Astro Configuration API Documentation:
// https://docs.astro.build/reference/configuration-reference
// @type-check enabled!
// VSCode and other TypeScript-enabled text editors will provide auto-completion,
// helpful tooltips, and warnings if your exported object is invalid.
// You can disable this by removing "@ts-check" and `@type` comments below.
import { imagetools } from 'vite-imagetools';
// @ts-check
export default /** @type {import('astro').AstroUserConfig} */ ({
// Comment out "renderers: []" to enable Astro's default component support.
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte'],
vite: { // for example only - adapt for your own project
plugins: [imagetools({ force: true })],
},
buildOptions: {
sitemap: true,
site: 'https://example.com/',
},
});
On top top you will add any vite
plugins you use in here too. For example, you might want to style your app with vanilla-extract. We include the vite-imagetools
plugin here just as an example of how to add a vite plugin to the config. Astro switched from snowpack tooling to vite in version 0.21. So, if you find content referencing a snowpack config file ignore this.
package.json
: this is the regular file you will be familiar with from other projects. In here you will find defaultdev
,build
andpreview
scripts. It is worth building locally frequently as you develop to check things work as expected. To do this, stop your dev server then runpnpm run build
and finallypnpm run preview
to spin up the built static site.sandbox.config.js
: config for running a CodeSandbox.
public and src folders
public/
this is like thestatic
folder in SvelteKit. You can throw your PWAmanifest.json
file, favicons androbots.txt
files in here. In essence, the folder is for anything which does not need processing by vite or Astro.src/
: this is where your app files will go.src/pages
is used for file-based routing, which you might already be familiar with from NextJS or SvelteKit. Essentially when you create a file in this folder, your app will have a page on the equivalent route. Sosrc/pages/about.astro
content will appear on thehttps://example.com/about/
page of your final app..astro
files contain markup for your pages. You can consider the code in them to be a superset of HTML, meaning valid HTML code works in them and there are some extra features. We will look into these in a little more detail below.
π 10 Quick Astro Features / Gotchas
- Astro supports TypeScript out of the box.
- Astro supports Markdown input files out of the box as well as remark and rehype plugins.
- You need to install the React renderer to use React components:
pnpm add -D @astrojs/renderer-react react react-dom
remember also to add this to the renderers
array in astro.config.mjs
(see above).
- You need to install the Svelte renderer to use Svelte components:
pnpm add -D @astrojs/renderer-svelte
As with React, remember also to add this to the rendered
array in astro.config.mjs
(see above).
You can install all your dependencies as dev dependencies (e.g.
pnpm i -D my-package
). Like SvelteKit Astro used ES Modules and vite takes care of bundling packages.We add global scripts to our Astro page header or components. Either way, if you include the
hoist
key term the script will be included in the page head section. Even if you include the same script in multiple instances of a component, Astro will only add it once.
<script hoist src="https://js.hcaptcha.com/1/api.js?render=explicit" async defer ></script>
- You can add SEO meta directly to
.astro
page head sections, similarly to scripts:
<meta name="description" content={metadescription} />
<meta
name="robots"
content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"
/>
<link rel="canonical" href="https://example.com/page/" />
- We need to add the autoprefixer manually by including a
postcss.config.cjs
file in the project root folder:
module.exports = {
plugins: {
autoprefixer: {},
},
};
and also install the package itself:
pnpm add -D autoprefixer
- Astro has inbuilt ability to create an XML sitemap automatically on build. You can also set Astro to generate RSS feeds. For a sitemap, just update the config file thus:
export default /** @type {import('astro').AstroUserConfig} */ ({
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte'],
buildOptions: {
sitemap: true,
site: 'https://astro.build/',
},
});
- If you need access to the site build timestamp, for example in SEO components, add this snippet to the config file:
export default /** @type {import('astro').AstroUserConfig} */ ({
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte'],
vite: {
define: {
'process.env.VITE_BUILD_TIME': JSON.stringify(new Date().toISOString()),
},
},
});
then you can access this in a component via process.env.VITE_BUILD_TIME
.
π₯ What goes in an Astro File?
We mentioned earlier that Astro is a superset of HTML. This means you have access to the HTML head section in the .astro
markup files and can add stylesheets and scripts. If you are used to React this is a little different, as there is no need to add a package like react-helmet
to inject SEO markup, for example, to the HTML head section.
As well as the markup itself, the .astro
files have an initial frontmatter section. It is demarcated like Markdown frontmatter with three dashes (---
) at the start and end. However, it is more flexible than Markdown frontmatter and allows JavaScript and even top level await (no need to wrap your async function expressions as an IIFE). You use the frontmatter section to import components. You can import React and Svelte components into the same file. Import using the familiar JavaScript syntax, just use the filename as it appears on the disk (this is contrary to how earlier Astro version worked, which you may see in old example code).
---
import ExampleReactComponent from '../components/exampleReactComponent.tsx';
import ExampleSvelteComponent from '../components/exampleSvelteComponent.svelte';
import { Markdown } from 'astro/components';
import BaseLayout from '../layouts/BaseLayout.astro';
---
<html lang="en-GB">
<head>
<title>RodneyLab Minimal Astro Example</title>
<style>
.container {
display: flex;
flex-direction: column;
background: hsl(
var(--colour-dark-hue) var(--colour-dark-saturation) var(--colour-dark-luminance)
);
color: hsl(
var(--colour-light-text-hue) var(--colour-light-text-saturation)
var(--colour-light-text-luminance)
);
padding: var(--spacing-8) var(--spacing-0) var(--spacing-32);
}
</style>
</head>
Now we knows the basics, let's look at some of the Astro aspects in more detail.
π 10 Tips for Getting Started with Astro
1. Getting Started with Astro: VSCode Integration
Astro have created an official VSCode plugin. You can find it in VSCode by going to *View / Extensions * then searching for Astro. The official extension has a blue tick and the publisher is Astro. This extension will give you syntax highlighting. For more detail see the extension in the VSCode market place.
2. Learning Astro: Prettier Formatting Extension
There is also an official prettier plugin for formatting .astro files. You can use your usual extensions for formatting Markdown, React, Svelte.
pnpm add -D prettier prettier-plugin-astro
Consider setting up a couple of package.json
scripts so you can format conveniently from the command line and also integrate formatting into your continuous integration process using Husky:
{
"name": "getting-started-astro",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"format": "prettier --write --plugin-search-dir=. .",
"prettier:check": "prettier --check --plugin-search-dir=. ."
},
So to format all files in the project you can just run pnpm run format
.
3. Getting Started with Astro: Favicon
The minimal starter code includes a rel
tag for a favicon in ico format. If you prefer to use a PNG file you can, of course, just replace this. Either way, place your favicon file, whether it is in PNG or ICO format in the public
folder of your project and it will be included in your app build. If you are building a Progressive Web App add all the icons needed together with the manifest.json
file to the public
directory.
Instead of adding this rel
tag to each route file in your project, use Astro layout files. You can use these for content which is the same on many pages. As an example you can normally include headers and footers in a layout file. The expected location for layouts is a src/layouts
folder. Here we define BaseLayout.astro
and include the rel
tag (for the favicon) in the HTML head
section:
<html lang="en-GB">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
</head>
<body>
<header>
<!-- header content -->
</header>
<main>
<slot />
</main>
<footer>
<!-- header content -->
</footer>
</body>
<html>
We then import this layout into any page files in which we want to use it. In this example code above, we include the header and footer in the layout. Then the <slot />
tag serves as a placeholder for content from whichever pages chose to use the layout. As an example we could use this layout on our home page like so:
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout>
<h1>Home Page</h1>
<p>Home page content.</p>
</BaseLayout>
Anything inside the BaseLayout
tags gets inserted into the layout in place of <slot />
. You see this cuts down on repeating boilerplate code.
4. Getting Started with Astro: Self-hosted Fonts
Self hosting fonts can make your pages load faster. The fontsource project supports many Google fonts and other open source fonts, making it easy to self-host in your project. You can download the packages for any fonts you want to use. For fonts which you use on every page, import in a layout file:
pnpm add @fontsource/montserrat
---
import '@fontsource/montserrat';
---
5. Getting Started with Astro: Server or Browser?
At some point when working in React or Svelte, you will need to add a check to see if your code is running on the (build or dev) server or actually in the browser. As an example you might run a media query in your React code to check if the user prefers reduced motion. This involves accessing the window
object, which will not be defined on the server. Astro has your back here. To stop your code crashing, you can include a check to make sure it only runs on the browser:
const ssr = import.meta.env.SSR;
const reduceMotion = !ssr && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
Here ssr
will be false when code runs in browser. On the server, shortcut evaluation will result in reduceMotion
being set to false without evaluating the second part where window
is used.
6. Learning Astro: Environment Variables
Environment variables offer a way to use private keys in your code, without including them in the files which you commit, for example to GitHub. Typically you will need to include some environment variables which you need exposed on the client. As an example, if you use hCaptcha on your forms, there is a site key which you need to submit from the user's browser to hCaptcha servers for each challenge. You will also have some secret variables which you will never want exposed on the client. To avoid accidentally exposing these secret variables, Astro, makes you add a PUBLIC_
prefix to environment variables which you have to expose on the client. You put these in the .env
file as usual:
PUBLIC_TWITTER_USERNAME="@askRodney"
For convenience, you might import all client environment variables into a single configuration file:
const website = {
twitterUsername: import.meta.env.PUBLIC_TWITTER_USERNAME ?? '',
};
export default website;
and then use them in any client side code where needed:
import website from '../../configuration/website';
const { twitterUsername } = website;
7. Getting Started with Astro: Node Packages
You can use node inbuilt packages (fs
, path
, etc.) in Astro, you just need to prefix them with node:
. As an example in this snippet from a .astro
file frontmatter, we look for blog posts in a particular directory in our project:
import fs from 'node:fs';
import path from 'node:path';
const __dirname = path.resolve();
const BLOG_PATH = path.join(__dirname, 'content/blog');
const directories = fs
.readdirSync(BLOG_PATH)
.filter((element) => fs.lstatSync(`${BLOG_PATH}/${element}`).isDirectory());
You can also use this in your astro.config.mjs
file. A great use case is to add the node path module to help you define path aliases. Path aliases save you having to type out full relative path prefixes (like ../../
) and make refactoring easier. You can define them in your Astro config file:
import path from 'node:path';
// @ts-check
export default /** @type {import('astro').AstroUserConfig} */ ({
// Comment out "renderers: []" to enable Astro's default component support.
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte'],
buildOptions: {
sitemap: true,
site: 'https://astro.build/',
},
vite: {
resolve: {
alias: {
$src: path.resolve('./src'),
$components: path.resolve('./src/components'),
$layouts: path.resolve('./src/layouts'),
},
},
define: {
'process.env.VITE_BUILD_TIME': JSON.stringify(new Date().toISOString()),
},
},
});
Now, in your source files you can replace:
---
import ExampleReactComponent from '../components/exampleReactComponent.tsx';
import ExampleSvelteComponent from '../components/exampleSvelteComponent.svelte';
import { Markdown } from 'astro/components';
import BaseLayout from '../layouts/BaseLayout.astro';
---
with:
---
import ExampleReactComponent from '$components/exampleReactComponent.tsx';
import ExampleSvelteComponent from '$components/exampleSvelteComponent.svelte';
import { Markdown } from 'astro/components';
import BaseLayout from '$layouts/BaseLayout.astro';
---
Props to folks in the Astro discord for this tip.
8. Getting Started with Astro: Styling
Astro supports styled components, vanilla-extract, scss and many other flavours of styling. Since we can't get through all of those here, let's look at good old CSS! For your Svelte components, you can include scoped CSS styles like you would in SvelteKit, for example. For React you can include styles inline, or opt for CSS modules. In the demo code, we went for another Astro alternative. We define the scoped styles in the .astro
file which includes the React component. For this to work, we need to use the :global
selector. Here we have the styles defined in head section of the .astro
file:
<html lang="en-GB">
<head>
<title>RodneyLab Minimal Astro Example</title>
<style>
:global(.react-container) {
display: flex;
flex-direction: column;
background: hsl(
var(--colour-secondary-hue) var(--colour-secondary-saturation)
var(--colour-secondary-luminance)
);
align-items: center;
width: 100%;
padding: var(--spacing-8) var(--spacing-0);
color: hsl(
var(--colour-light-text-hue) var(--colour-light-text-saturation)
var(--colour-light-text-luminance)
);
}
:global(.react-container-alt) {
background: hsl(
var(--colour-alternative-hue) var(--colour-alternative-saturation)
var(--colour-alternative-luminance)
);
}
:global(.react-button) {
background: hsl(
var(--colour-alternative-hue) var(--colour-alternative-saturation)
var(--colour-alternative-luminance)
);
}
:global(.react-button-alt) {
background: hsl(
var(--colour-secondary-hue) var(--colour-secondary-saturation)
var(--colour-secondary-luminance)
);
}
</style>
</head>
<body>
<ExampleReactComponent />
</body>
</html>
Then as you might expect, we attach the classes to the React component itself:
import React, { useState } from 'react';
import type { FC } from 'react';
export const ReactExample: FC<{}> = function ReactExample() {
const [altColours, setAltColours] = useState<boolean>(false);
return (
<section className={`react-container${altColours ? ' react-container-alt' : ''}`}>
<h2>Example React Component</h2>
<div className="video-container">
<\iframe
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/PJ0QSJpJn2U"
title="Should you Stop Using React"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</div>
<button
className={`react-button${altColours ? ' react-button-alt' : ''}`}
onClick={() => {
setAltColours(!altColours);
}}
>
<span className="screen-reader-text">Toggle colours</span>
</button>
</section>
);
};
export default ReactExample;
We only use TypeScript here to show that Astro supports TypeScript out of the box. You can just as easily use regular JSX instead.
You will notice some other classes in the code. We defined related styles for these in a global CSS file together with global variables. We include this from the head section of our layout file:
---
import '@fontsource/montserrat';
---
<html lang="en-GB">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/styles.css')} />
<meta name="viewport" content="width=device-width" />
</head>
<body>
<slot />
</body>
</html>
For scoped CSS to be applied to components within the same file, just include styles in a <style>
block in the page header (:global
selector is not necessary, in this case, as it is for child components).
9. Getting Started with Astro: ESLint
Because you import everything as a dev dependency (perfectly valid), if you use ESLint, you may get a warning about importing extraneous dependencies. You can just switch this off by adding the following rule to you config file:
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'plugin:react/recommended',
'plugin:jsx-a11y/recommended',
'airbnb',
'plugin:prettier/recommended',
'prettier',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint', 'jsx-a11y', 'prettier'],
rules: {
'prettier/prettier': 'error',
'react/jsx-filename-extension': [1, { extensions: ['.tsx'] }],
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
},
};
10. Getting Started with Astro: Responsive, Next-Gen Images
A fantastic plugin for image handling in vite is vite-imagetools
. There's not really time to get into it here, without the post getting too long. I hope to be able to write a separate post on using it with Astro soon. It will generate images for you in Next-Gen formats and creates hashes. It also creates images in different responsive sizes to include in srcset
lists.
ππ½ Wrapping Up: Demo Playground
Although this is not a tutorial as such, most of the code samples came from a demo app. There are Svelte and React components in it as well as some Markdown. Each of these three includes an iframe
with a YouTube video:
<body>
<BaseLayout>
<header></header>
<main class="container">
<h1>Minimal <a href="https://astro.build/">Astro</a> Example</h1>
<p>This demo is not endorsed by Ben Awad, just thought the video content was fitting!</p>
<ExampleReactComponent />
<ExampleSvelteComponent />
<section class="mdx-container">
<Markdown>
## Astro in 100 Seconds
<div class="video-container">
</iframe
title="Astro in 100 Seconds"
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/dsTXcSeAZq8"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
</Markdown>
</section>
</main>
</BaseLayout>
</body>
</html>
We mentioned earlier that Astro ships no JavaScript by default. To hydrate a component in Astro you add client:load
to the component's element (this is different to earlier versions of Astro where you used a <Component:load>{content}</Component:load>
syntax). Anyway, you will notice that we don't have those attributes added in our code yet. However, the videos still play. Interestingly this is because they are iframes and so treated as independent documents. In a real world app, you would want to lazy load these or at least have more control over how they loaded. As it stands the page loads a little slowly, but remember this is a playground rather than a production app.
Beneath the React and Svelte videos there is a button which should toggle the background colour. Try cloning the repo and adding client:load
to get a feel for how Astro works:
<ExampleReactComponent client:load/>
<ExampleSvelteComponent client:load />
The code for the demo is on GitHub. You can open the playground in StackBlitz or just play locally:
mkdir getting-started-with-astro && cd $_
pnpm init astro -- --template https://github.com/rodneylab/astro/tree/main/demos/getting-started-astro
pnpm install
pnpm run dev
Also experiment with the other hydration props (you will need to tinker with the code for some of these to have a perceptible effect):
client:idle
client:visible
client:media={QUERY}
client:only
See Astro docs for more details on how these work.
I hope you found this article useful and am keen to hear how you plan to use Astro.
ππ½ Getting Started with Astro: Feedback
Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please consider supporting me through Buy me a Coffee.
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram. Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as SvelteKit. Also subscribe to the newsletter to keep up-to-date with our latest projects.