DEV Community

Cover image for What Is Astro? An Introduction to the Popular Static Site Generator
Kinsta
Kinsta

Posted on • Originally published at kinsta.com

What Is Astro? An Introduction to the Popular Static Site Generator

Web development has evolved significantly from static single-page websites to offering a variety of languages, frameworks, and systems for every niche.

Now we have Astro, a new and easy-to-use tool by Fred K. Schott and a group of other contributors. Developers love it because it’s simple like old-school web pages but powerful.

We'll explain why it's popular and how you can use Astro to easily make a blog!

What Is Astro?

Astro, or Astro.js, is a popular static site generator conceived for those who want to create content-rich websites that run quickly and smoothly. Its lightweight nature, intuitive structure, and gentle learning curve make it attractive to developers of all experience levels.

Despite its small footprint, Astro comes with powerful tools that drastically increase your site’s flexibility, saving you hours in content and theme management. In addition, it gives developers the option of working with their preferred frameworks in conjunction with Astro — an appealing prospect for seasoned coders who already have a host of favorites.

Here are just a few of the ways Astro stands out from the crowd:

  • Island architecture: Astro extracts your user interface (UI) into smaller, isolated components known as “Astro Islands” that can be used on any page. Unused JavaScript is replaced with lightweight HTML.

  • Zero JavaScript (by default): While you can use all the JavaScript you want to create your websites, Astro will attempt to deploy zero JavaScript to production by transcribing your code for you. This a perfect approach if your focus is on site speed.

  • SSG and SSR included: Astro started as a static site generator, but along the way, it became a framework that uses both static site generation (SSG) and server-side rendering (SSR). And you can pick which pages will use which approach.

  • Framework-agnostic: When using Astro, you can use any JavaScript framework you like — even multiple frameworks at once. (We’ll discuss this in greater detail later in this article.)

What’s more, Astro is edge-ready, meaning it can be deployed anywhere, anytime, with ease.

Astro’s Structure

Before we venture any further, it’s important to understand how Astro is set up so you can use it effectively. Let’s take a look at Astro’s core file structure:

├── dist/
├── src/
│   ├── components/
│   ├── layouts/
│   └── pages/
│       └── index.astro
├── public/
└── package.json
Enter fullscreen mode Exit fullscreen mode

As you can see, the structure itself is quite simple. However, there are some key points you should remember:

  • Most of our project lives in the src folder. You can arrange your components, layouts, and pages into subfolders. You may add additional folders to make your project easier to navigate.

  • The public folder is for all the files that live outside of the build process, such as fonts, images, or a robots.txt file.

  • The dist folder will contain all the content you want to deploy on your production server.

Next, let’s dive deeper into Astro’s main constituents: components, layouts, and pages.

Components

Components are reusable chunks of code that can be included all over your website, similar to shortcodes in WordPress. By default, they have the file extension .astro, but you can also use non-Astro components built with Vue, React, Preact, or Svelte.

The following is an example of what a simple component looks like — in this case, a classed div tag containing an h2:

<!-- src/components/Kinsta.astro -->
<div class="kinsta_component">
    <h2>Hello, Kinsta!</h2>
</div>
Enter fullscreen mode Exit fullscreen mode

And here’s how we can incorporate that component into our site:

---
import KinstaComponent from ../components/Kinsta.astro
---
<div>
    <KinstaComponent />
</div>
Enter fullscreen mode Exit fullscreen mode

As demonstrated above, you first have to import the component. Only then can it be included on the page.

Now it’s time to add some properties to our component. Let’s start with a {title} property:

---
const { title = 'Hello' } = Astro.props
---

<div class="kinsta_component">
    <h2>{title}</h2>
</div>
Enter fullscreen mode Exit fullscreen mode

And here’s how our property would be implemented:

---
import KinstaComponent from ../components/Kinsta.astro
---

<div>
    <!-- This shows "Good day" -->
    <KinstaComponent title="Good day"/>

    <!-- This shows "Hello" -->
    <KinstaComponent />
 </div>
Enter fullscreen mode Exit fullscreen mode

Simple, right?

As you’ve probably already realized, the real power of Astro’s components is in their global and reusable nature. They enable you to make sweeping changes to your entire site by editing only a few lines of code, which can save you countless hours that would otherwise be spent on tedious, painstaking text replacements.

Layouts

Now, let’s talk about layouts. In addition to their familiar thematic function, layouts in Astro are also reusable components, but they’re employed as code wrappers.

Take a look at this example:

---
// src/layouts/Base.astro
const { pageTitle = 'Hello world' } = Astro.props
---

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width">
    <title>{pageTitle}</title>
</head>
<body>
    <main>
        <slot />
    </main>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Note the <slot /> tag here. The <slot /> element in Astro acts as a placeholder for actual HTML tags and content.

Let’s see it in action.

The code below shows our <slot /> tag getting replaced with our desired code, all of which is wrapped by our Base.astro layout:

---
import Base from '../layouts/Base.astro';
---

<Base title="Hello world">
    <div>
        <p>Some example text.</p>
    </div>
</Base>
Enter fullscreen mode Exit fullscreen mode

As you can see, our <slot /> tag was replaced by the HTML it represents, which is:

<div>
    <p>Some example text.</p>
</div>
Enter fullscreen mode Exit fullscreen mode

As you can see, layouts, like components, allow you to reuse chunks of code across your site, simplifying the challenge of updating your global content and design.

Pages

Pages are a special type of component that is responsible for routing, data loading, and templating.

Astro uses file-based routing to generate pages, rather than dynamic routing. Not only does the file-based method consume less bandwidth, but it also saves you from having to import your components manually.

Here’s an example of defined routes:

src/pages/index.astro => yourdomain.com
src/pages/test.astro => domain.com/test
src/pages/test/subpage => domain.com/test/subpage
Enter fullscreen mode Exit fullscreen mode

With these routes, our resulting homepage would be rendered as follows:

<!-- src/pages/index.astro -->
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width">
    <title>Hello World</title>
</head>
<body>
    <h1>Hello, Kinsta</h1>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

But we already know how to use layouts, so let’s convert this into something that’s globally accessible:

---
import Base from '../layouts/Base.astro';
---

<Base>
    <h1>Hello, Kinsta</h1>
</Base>
Enter fullscreen mode Exit fullscreen mode

There – that’s much cleaner.

We’ll discuss routing in Astro in more detail later in this article, but for now, let’s move on to the fun stuff: site construction and customization.

Customizing and Extending Astro

It’s time to learn how to customize your Astro site! We’re going to use Markdown collections, routing, image handling, and an integration with React to build out and personalize our static site.

🎈

Unlock a new level of performance and ease with Kinsta

Find out why bloggers, agencies, and Fortune 500 companies are choosing Kinsta to host, build, and launch their websites, applications, and databases.

Book a Demo

Markdown Collections

With version 2.0, Astro introduced a much better way to maintain Markdown content than before. Thanks to collections, we can be sure that all our frontmatter data is included and has the correct type of association.

Lately, in version 2.5, they added a possibility to also manage JSON and YAML files as collections.

Ready to get your hands dirty?

First, put all your Markdown articles in the src/content/collection_name folder. We’re going to create a blog collection for this project, so in our demonstration, the folder will be src/content/blog.

Now it’s time to define all the required frontmatter fields in our src/content/config.ts file. Our blog will need the following:

  • title (string)

  • tags (array)

  • publishDate (time)

  • image (string, optional)

This is what it all looks like put together:

import { z, defineCollection } from 'astro:content';

const blogCollection = defineCollection({ 
    schema: z.object({
        title: z.string(),
        tags: z.array(z.string()),
        image: z.string().optional(),
        publishDate: z.date(),
    }),
});

export const collections = {
    'blog': blogCollection,
};
Enter fullscreen mode Exit fullscreen mode

And this is what our article-about-astro.md Markdown file contains:

---
title: Article about Astro
tags: [tag1, tag3]
publishDate: 2023-03-01
---
## Tamen risit

Lorem *markdownum flumina*, laceraret quodcumque Pachyne, **alter** enim
cadavera choro.
Enter fullscreen mode Exit fullscreen mode

True, there’s nothing special about our Markdown file. But there’s some hidden magic here that will manifest if we make a typo.

Let’s say, for instance, that instead of typing publishDate, we accidentally typed publishData. In the case of a misspelling like this, Astro will throw an error:

blog → article-about-astro.md frontmatter does not match collection schema.
  "publishDate" is required.
Enter fullscreen mode Exit fullscreen mode

Amazing, right? This nifty feature can help us find errors relating to frontmatter in a matter of seconds.

The last thing we need to add is a page showing our data. Let’s create a file at src/page/blog/[slug].astro with the following code:

---
import Base from '../../layouts/Base.astro';
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
    const blogEntries = await getCollection('blog');
    return blogEntries.map(entry => ({
        params: { slug: entry.slug }, props: { entry },
  }));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<Base>
    <h1>{entry.data.title} </h1>
    <Content />
</Base>
Enter fullscreen mode Exit fullscreen mode

Thanks to getStaticPaths, Astro will create all the static pages for each post in the blog collection.

The only thing we’re missing now is a listing of all our articles:

---
import Base from '../../layouts/Base.astro';

import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog');
---
<Base>
<ul>
    {blogEntries.map(item => <li> <strong><a href={'/blog/' + item.slug}>{item.data.title}</a></strong></li>)}
</ul>
</Base>
Enter fullscreen mode Exit fullscreen mode

As you can see, using collections makes this task remarkably simple.

Now, let’s create a data type collection. First, we must open the src/content/config.ts file again and add a new data collection:

import { z, defineCollection, referenece } from 'astro:content';

const blogCollection = defineCollection({ 
    type: 'content',
    schema: z.object({
        title: z.string(),
        tags: z.array(z.string()),
        image: z.string().optional(),
        publishDate: z.date(),
        author: reference('authors')
    }),
});

const authorsCollection = defineCollection({ 
    type: 'data',
    schema: z.object({
        fullName: z.string(),
        country: z.string()
    }),
});


export const collections = {
    'blog': blogCollection,
'authors': authorsCollection,
};
Enter fullscreen mode Exit fullscreen mode

Apart from creating a new collection, we also added the author reference in the blogCollection.

Time to create a new author. We must create a file called maciek-palmowski.json in the content/authors.json:

{
    "fullName": "Maciek Palmowski",
    "country": "Poland"
}
Enter fullscreen mode Exit fullscreen mode

The last thing left is to grab this data in our Post. To do so, we’ll need to use getEntry:

---
import Base from '../../layouts/Base.astro';
import { getCollection, getEntry } from 'astro:content';
export async function getStaticPaths() {
  const blogEntries = await getCollection('blog');
  return blogEntries.map(entry => ({
    params: { slug: entry.slug }, props: { entry },
  }));
}
const { entry } = Astro.props;
const author = await getEntry(entry.data.author);
const { Content } = await entry.render();
---
<Base>
<h1>{entry.data.title}</h1>
<h2>Author: {author.data.fullName}</h2>
<Content />
</Base>
Enter fullscreen mode Exit fullscreen mode

Routing

Astro has two different routing modes. We already learned about the first – static (file-based) routing – when we covered pages earlier.

Now we’re going to shift our focus to dynamic routing.

Using dynamic route parameters, you can instruct an Astro page file to automate the creation of multiple pages with the same structure. This is useful when you have a lot of one particular type of page (think author bios, user profiles, documentation articles, and so on).

For this next example, we’ll work on generating bio pages for our authors.

In Astro’s default static output mode, these pages are generated at build time, meaning you must predetermine the list of authors that get a corresponding file. In dynamic mode, on the other hand, pages are generated upon request for any route that matches.

If you want to pass a variable as your filename, add brackets around it:

pages/blog/[slug].astro -> blog/test, blog/about-me 
Enter fullscreen mode Exit fullscreen mode

Let’s dive deeper into this using the code from our src/page/blog/[slug] file:

---
import Base from '../../layouts/Base.astro';
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
    const blogEntries = await getCollection('blog');
    return blogEntries.map(entry => ({
        params: { slug: entry.slug }, props: { entry },
  }));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<Base>
    <h1>{entry.data.title}</h1>
    <Content />
</Base>
Enter fullscreen mode Exit fullscreen mode

The getStaticPaths route is responsible for generating all the static pages. It returns two objects:

  • params: Used to fill the brackets in our URLs

  • props: All the values we’re passing to the page

And with that, your page generation is taken care of.

Image Handling

We can’t talk about performant websites without bringing up modern image formats, correct resizing methods, and lazy loading.

Luckily, Astro’s got us covered here, too. Thanks to the @astrojs/image package, we can introduce all the above in a matter of minutes.

After installing the package, we gain access to two components: Image and Picture.

The Image component is used to create an optimized <img /> tag. Here’s an example:

---
import { Image } from '@astrojs/image/components';
import heroImage from '../assets/hero.png';
---

<Image src={heroImage} format="avif" alt="descriptive text" />
<Image src={heroImage} width={300} alt="descriptive text" />
<Image src={heroImage} width={300} height={600} alt="descriptive text" />
Enter fullscreen mode Exit fullscreen mode

Similarly, the Picture component creates an optimized <picture/> component:

---
import { Picture } from '@astrojs/image/components';
import hero from '../assets/hero.png';
---
<Picture src={hero} widths={[200, 400, 800]} sizes="(max-width: 800px) 100vw, 800px" alt="descriptive text" />
Enter fullscreen mode Exit fullscreen mode

SSG vs SSR

By default, Astro runs as a static site generator. This means that all the content is converted to static HTML pages.

While this is a perfect approach from many perspectives (especially speed-related), we might sometimes prefer a more dynamic approach. If you want a separate profile page for each user, for example, or if you have thousands of articles on your site, re-rendering everything each time would be far too time-consuming.

Luckily, Astro also can work as a fully server-side-rendered framework instead or in a hybrid mode between the two.

To enable side-wide SSR, we need to add the following code to astro.config.mjs:

import { defineConfig } from 'astro/config';

export default defineConfig({
    output: 'server'
});
Enter fullscreen mode Exit fullscreen mode

This is the standard approach.

The hybrid approach means that by default, everything is dynamically generated, apart from the pages with export const prerender = true added.

With Astro 2.5, there is also the possibility to set static rendering as default and select dynamic routes manually.

Thanks to those, we can, for example, create a fully statically generated website with dynamic login and profile pages. Neat, right?

You can read more about this in the official documentation.

Integrating Other JavaScript Frameworks

Another amazing feature of Astro allows you to bring along your favorite framework and use it in concert with Astro. You can mix Astro with React, Preact, Svelte, Vue, Solid, or Alpine (for all integrations, see Astro’s “Add Integrations” documentation).

Let’s say we want to use React. First, we need to install the integration by running the following in npm:

npx astro add react
Enter fullscreen mode Exit fullscreen mode

Now that React has been integrated, we can create a React component. In our case, it will be the counter component at src/components/ReactCounter.tsx:

import { useState } from 'react';

/** A counter written with React */
export function Counter({ children }) {
    const [count, setCount] = useState(0);
    const add = () => setCount((i) => i + 1);
    const subtract = () => setCount((i) => i - 1);

    return (
        <>
            <div className="counter">
                <button onClick={subtract}>-</button>
                <pre>{count}</pre>
                <button onClick={add}>+</button>
                </div>
            <div className="counter-message">{children}</div>
        </>
    );
}
Enter fullscreen mode Exit fullscreen mode

Last but not least, we need to place the counter on our page with the following code:

---
import * as react from '../components/ReactCounter';
---
<main>
    <react.Counter client:visible />
</main>
Enter fullscreen mode Exit fullscreen mode

And voilà: Your React component has been integrated seamlessly into your site.

How To Deploy Astro Using Kinsta

Now it’s time to get our Astro site onto the web. Luckily, Kinsta is the perfect host for a quick and painless deployment.

Start by creating a GitHub repository for your site’s files. If you’re not ready to use your own files, you can clone this Astro starter site template our team created.

GitHub repo of Astro starter site template by Kinsta

GitHub repo of Astro starter site template by Kinsta

Once your repo is ready, log in to MyKinsta, select Applications at the left, and then choose Application from the purple Add service dropdown.

Add an application in MyKinsta

Add an application in MyKinsta

The final step involves you supplying Kinsta with the details of your build and deployment.

Most of what you’ll be asked, such as Process name and Payment method, will have obvious or simple answers. Note that you may leave the Start command field empty if you choose; Kinsta will automatically assign npm start as the command.

If you’re unsure how to respond to any other prompt, refer to Kinsta’s documentation for field-specific guidance and Astro deployment examples. You can also check out Astro’s own guide to deploying at Kinsta.

Once you’ve finished entering your build’s details, click the Confirm payment method button to initialize your build.

And that’s it! You now have a live, fully functioning static site created with the Astro framework.

A dark page with the Kinsta logo in white in the center above the words

Our live Astro homepage

You can find your live URL and other deployment details under Deployments in your MyKinsta account.

The

A successful Astro deployment

Summary

Astro’s clear structure, simple syntax, and global components make building and running an application really easy. , Its lightweight nature and dual use of static and dynamic routing dramatically increase site responsiveness, while its capacity for cooperating with and alongside other JavaScript frameworks makes it all the more appealing to experienced coders.

If your goal is to create a content-rich site that loads quickly, grants modular functionality, and provides both static and dynamic generation, then Astro could be the right choice for you.

You can host your static website with Kinsta’s Application Hosting for free, and if you like it, upgrade to our Hobby Tier plan.

💡 What are your thoughts on the Astro static site generator? Have you used it on a project of your own? Let us know in the comments section below.

Top comments (2)

Collapse
 
diomed profile image
May Kittens Devour Your Soul

it's your right to advertise kinsta.

that's what you did.

kinda even more than talking about Astro.

my advice to you is to update that astro template to support Astro v. 3 and higher.
at least update versions of software you present.

Collapse
 
palmiak profile image
Maciek Palmowski

Hey

Yes, you are right about Astro 3.0 introducing a breaking change. Of course, I'm sure that you know that the astrojs/image isn't required anymore. We will update this part.

As for the starter template, it's already updated. Thanks.