Skip to main content
Getting started with Astro Framework
22 min read

Getting started with Astro Framework

This article was last updated on October 22, 2024 to include advanced routing techniques, and using with GraphQL

Introduction

Astro is one of the newest front-end frameworks. You can use it to build performant, content-driven websites such as blogs and e-commerce sites optimized for search engines. It combines static site generation and server-side rendering.

Astro is the all-in-one web framework designed for speed. Pull your content from anywhere and deploy it everywhere, all powered by your favorite UI components and libraries. - Astro

In this article, we will take a deep dive into the Astro framework, explore its key features, and guide you through the process of setting up your first Astro project.

By the end of this article, you will be able to use Astro to build performant and scalable websites and applications.

What we'll cover:

Prerequisites

You need to install the tools below to try out the examples in this article.

  • Node runtime environment: If you haven't, install the Node runtime environment from the Node downloads page. At the time of writing this article, you need to have Node version 18.14.1 or higher. Recent Node versions also bundle the npm package manager out of the box.
  • Visual Studio Code: If you haven't, install Visual Studio code from the VS Code downloads page. You can also use another text editor or IDE of your choice.
  • Astro VS Code extension: Astro has a VS Code extension that can significantly improve your development experience. After installing VS Code, install the VS Code extension for Astro from the marketplace. It has features such as syntax highlighting, code completion, and code formatting. It's a must-have plugin when using VS Code to write Astro code. Astro has similar plugins for other text editors and IDEs such as JetBrains and Vim. Check the setup docs for details.

You now have everything you need to start working with Astro. In the next section, you will learn how to create a new Astro project from scratch.

How to Create Your First Astro Project

In this section, you will learn how to set up a simple Astro project on your machine. The fastest way to set up an Astro project is to use the astro command line tool. You can also install Astro manually. However, we won't explore manual installation in this article. If you want to install it manually, check out the Astro documentation.

To create a new Astro project using the command line tool, run the command below on the terminal.

# npm
npm create astro@latest

# pnpm
pnpm create astro@latest

# yarn
yarn create astro

The command above will launch the Astro commandline wizard. You need to respond to the command line prompts as in the example below.

✔ Where should we create your new project? ./cosmic-conjunction
✔ How would you like to start your new project? Include sample files
✔ Do you plan to write TypeScript? No
✔ Install dependencies? Yes
✔ Initialize a new git repository? No

After creating your Astro project and installing dependencies, you can open the project directory in VS Code or any other text editor and run the command below to launch the development server.

# npm
npm run dev

# pnpm
pnpm run dev

# yarn
yarn run dev

Now, you have successfully created your first Astro project. You will learn about the Astro project structure in the next section.

Astro Project Structure

Astro has an opinionated project structure. Most Astro projects will require you to organize your files and directories as follows:

├── README.md
├── astro.config.mjs
├── package-lock.json
├── package.json
├── public
│ ├── favicon.svg
│ ├── index.css
│ └── index.js
├── src
│ ├── components
│ │ └── App.astro
│ ├── layouts
│ │ └── default.astro
│ ├── pages
│ │ ├── 404.astro
│ │ └── index.astro
│ └── styles
│ └── global.css
└── yarn.lock

This is the structure of a basic Astro project. Let's explore the files and directories above.

  • The astro.config.mjs file is the Astro project configuration file
  • The public directory contains the project static assets
  • The src directory contains the project source code
  • The components directory contains the project components
  • The layouts directory contains the project layouts
  • The pages directory contains the project pages
  • The styles directory contains the project styles

In the next section, you will learn about Astro layouts and pages.

How to define Pages and Layouts in Astro

Pages in Astro

Pages in Astro are similar to those in Next. They are responsible for your project routing. Every file in the pages directory is a page. For example, the index.astro file in the pages directory is the home page of your Astro project.

├── pages
│ ├── 404.astro
│ └── index.astro

Similarly, the 404.astro file is the 404 page of your Astro project.

Each file you create in the src/pages/ directory becomes an endpoint based on its file path. In the example below, the about.astro and contact.astro files are the about and contact pages respectively. You can access them at the /about and /contact paths respectively.

├── pages
│ ├── about.astro
│ ├── contact.astro
│ ├── blog
│ │ ├── [slug].astro
│ │ └── index.astro
│ └── index.astro

In the file structure above, the blog directory contains pages for your blog post. The /pages/blog/index.astro file is the home page for your blog and the [slug].astro file is the blog post page.

Astro supports dynamic routing. In the file structure above, we have blog folder with two files inside: index.astro and [slug].astro. The index.astro file maps to the /blog path and the [slug].astro file maps to the /blog/:slug path. The :slug is a dynamic parameter. If you've a blog post with the hello-world slug, then its URL path will be /blog/hello-world.

Therefore, the mapping between the file path and corresponding URL path will look like so:

/blog/index.astro         -> /blog
/blog/[slug].astro -> /blog/hello-world

This process is called file-based routing. It is a simple and intuitive way to define routes in Astro.

Layouts in Astro

Layouts in Astro are similar to HTML templates that provide a reusable UI structure. The default.astro file in the layouts directory is the default layout of the project.

├── layouts
│ └── default.astro

The default.astro file defines the layout for all pages of your Astro project. The code below shows what a typical default.astro file looks like:

---
import { Head } from 'https://astrojs.org'
---

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="/index.css" />
<script type="module" src="/index.js"></script>
</head>

<body>
<slot />
</body>
</html>

Astro will inject any content you wrap within a layout component into a slot. Astro slots are similar to ng-content in Angular and children prop in React.

Let's say we have the MyLayout.astro layout:

---
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
const { title } = Astro.props
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<BaseHead title="{title}" />
</head>
<body>
<nav>
<a href="#">Home</a>
<a href="#">Posts</a>
<a href="#">Contact</a>
</nav>
<h1>{title}</h1>
<article>
<slot />
<!-- your content is injected here -->
</article>
<footer />
</body>
</html>

You can render the above layout like so:

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

<MyLayout title="MyLayout">
<p>My layout!</p>
</MyLayout>

Astro will inject <p>My layout!</p> in place of the <slot /> element in the MyLayout component. It will also pass the title prop into the MyLayout component.

You will learn more about props in Astro in the next section.

How to Build Components with Astro

Components are the building blocks of all component-oriented frameworks like Astro.

Components are single units of UI. They are reusable and composable. This makes them easy to maintain and test. Your Astro project will always contain components. So Astro is a tree of components.

To create a component in Astro, create a file with the .astro extension. For example, Component.astro is an Astro component.

A basic component shell looks like so:

---
// Component Script (JavaScript)
---
<!-- Component Template (HTML + JS Expressions) -->

You write JavaScript code in the Component Script section. You need to enclose this section in a code fence. It is where you define the component logic.

The component template is written in HTML and you can use it for defining the component UI.

Let's create a component and name it MyComponent.astro:

<form action="post">
<input type="text" placeholder="Type your text here..." />
<button>Send</button>
</form>

This is a simple reusable component. It has a simple form element with an input field and a button. You can use it anywhere in your project.

The code below shows how you can import and render the above component anywhere in your project:

---
import MyComponent from '../components/MyComponent.astro';
---

<MyComponent />

How to add event handlers in Astro

Let's say we want to add a click event to the button in MyComponent. We know that the event handler will be a JavaScript function. Therefore, we will add a script tag to the component and write the JavaScript code in it.

<form>
<input type="text" placeholder="Type your text here..." />
<button id="submit-btn">Send</button>
</form>

<script>
function handleClick() {
console.log("Button clicked!");
}

document.querySelector("#submit-btn").addEventListener("click", handleClick);
</script>

We have added a click event handler to the button. The handleClick event handler is invoked when the button is clicked and it will log Button clicked! to the browser console.

How to Pass Props to Components in Astro

Props are one of the most important features in any component-oriented framework. Props are used to pass data from one component to another. This makes the components reusable and composable.

Let's see how we can pass props to an Astro component. We want to pass a button text to the MyComponent component. We will do this by rendering the component like so:

---
import MyComponent from '../components/MyComponent.astro';
---

<MyComponent buttonText="Send" />

In the code above, the prop name is buttonText and its value is "Send". You can pass any JavaScript data via props.

Let's see how you can access the prop in the MyComponent component.

---
const { buttonText } = Astro.props;
---
<form>
<input type="text" placeholder="Type your text here..." />
<button id="submit-btn">{buttonText}</button>
</form>

You can destructure the Astro.props object to access the prop.

The Astro object is available in all components and Astro puts all the props sent to that particular component in the component's Astro.props object. An Astro object instance is unique to each component.

Working with Static and Dynamic Content in Astro

Passing content to Astro components via slots

There are times when we want to render elements between the tags of a component:

<MyComponent>
<p>My component!</p>
</MyComponent>

With the code in our MyComponent component, the <p>My component!</p> will not be rendered. This is because the component template does not have a place to render the content. We will add a slot element to the component template:

---
const { buttonText } = Astro.props;
---
<form>
<input type="text" placeholder="Type your text here..." />
<button id="submit-btn">{buttonText}</button>
</form>

<slot />

Astro will render the element <p>My component!</p> in place of the slot element.

Astro has feature for naming slots so that you can render content in a specific slot. Let's say you want to render content at the start of the template and a footer at the end of the template. You can name the slots as header and footer:

---
const { buttonText } = Astro.props;
---
<slot name="header">
<form>
<input type="text" placeholder="Type your text here..." />
<button id="submit-btn">{buttonText}</button>
</form>
<slot name="footer" />

You can now render content in the header and footer slots like so:

<MyComponent>
<p slot="header">My component!</p>
<p slot="footer">My component!</p>
</MyComponent>

How to Manage Data in Astro

Managing data is a crucial aspect of web development, and Astro provides robust options for efficiently fetching and utilizing data within your projects. In this section, we will explore the various data management capabilities offered by Astro, including fetching data from external sources or APIs and leveraging that data within Astro templates and components.

Fetching Data from APIs in Astro

Astro provides the flexibility for retrieving data from external sources or APIs. You can utilize the full power of JavaScript to perform data fetching operations and seamlessly integrate the retrieved data into your Astro projects.

HTTP Requests You can make HTTP requests using popular JavaScript libraries such as fetch or axios in Astro. These libraries provide convenient methods for sending GET, POST, PUT, DELETE, and other types of requests to external APIs. By leveraging these libraries, you can retrieve data from RESTful APIs or GraphQL endpoints.

Data Fetching Libraries Astro also integrates well with data-fetching libraries like swr or react-query. These libraries provide additional features such as caching, automatic revalidation, and error handling, which can greatly simplify the process of fetching and managing data in our Astro projects.

Serverless Functions Another approach to fetching data in Astro involves using serverless functions. These functions run on the server side and allow us to fetch data from external sources or perform any necessary server-side processing. Astro supports serverless functions, enabling us to retrieve data dynamically and pass it to our Astro components during the build process.

---
import { getPosts } from '../lib/posts';
const posts = getPosts();
---

{posts.map((post) => (
<div>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}

With data management capabilities, you can fetch and utilize data from external sources or APIs, empowering you to create dynamic and data-driven websites or applications. Whether you need to display blog posts, populate product information, or showcase real-time data, Astro offers a versatile toolkit for managing data within your projects.

Nested Routes in Astro

In Astro, the routing is based on, but for more complex route handling, you can nest your pages inside of folders. Suppose you have a blog section, your routes can look something like this:

└── pages
│ ├── blog
│ │ ├── index.astro
│ │ └── [slug].astro
│ └── index.astro

Here's how it works:

  • blog/index.astro maps to /blog, which can be the main blog page.
  • blog/[slug].astro maps to /blog/:slug as a file-based dynamic route, with the :slug portion being any dynamic value, such as the ID or title of the blog post.

At this setup, you could think of a blog post like this: /blog/my-first-post.

Dynamic Routing with Astro

Astro also supports dynamic routing with URL parameters. As shown above, you have the ability to create dynamic routes with square brackets [ ] in your filenames. For example, [slug].astro gives you access to blog posts, products, or any other resource dynamically that would need to be fetched based on a URL.

Here is a simple example:

---
const { slug } = Astro.params;
const post = await getPostData(slug);
---

<h1>{post.title}</h1>
<p>{post.content}</p>
</article>

Below, Astro.params.slug retrieves the dynamic part of the URL so that you can render specific content for each post.

Programmatic Routing

While file-based routing handles most of the use cases, there are sometimes instances when you want more control over how routes are generated. You can use the advantages of programmatic routing when you may want to conditionally redirect users or generate routes dynamically from data—which could be from an API or a database.

Astro proposes to do this with the injectRoute() API, but programmatically injecting routes at build time is still an experimental feature.

Example:

import { injectRoute } from "astro";

injectRoute({
component: '/src/pages/[slug].astro',
params: { slug: 'my-dynamic-post' }
path: '/blog/my-dynamic-post',
});

It exposes a method from which you can inject any dynamic route to your app, basically acting on any of your custom logics.

Overview Routes with Redirection

You can also deal with redirects. Astro does not support redirects out of the box yet, but you could create the support yourself most easily with HTML meta tag in or with JavaScript inside of your components. Here is a simple example with JavaScript that redirects user to another page:

Alternatively,

<script>window.location.href = '/new-url';</script>

You could add this to any Astro page to do a client-side redirect.

Why This Matters

Advanced Routing in Astro gives you the ability to route more complex routing scenarios—especially in larger applications that need nesting, dynamic parameters, and even programmatically route in Astro. Scale your app's structure without complicating the overall architecture.

Why you should use Astro?

The benefits of using Astro are numerous. We will explore some of them in this section.

Astro is a component-based front-end framework

Astro is a component-based framework. You can use it to build reusable UI components. This modular approach not only enhances code organization but also facilitates collaboration among team members, making it easier to maintain and scale projects.

Astro scores highly in core web vitals

In 2023, Astro was compared with other frameworks in terms of performance. Astro, Next, Nuxt, SvelteKit, WordPress, Remix, and Gatsby were compared against major core vitals assessments.

In the FID, First Input Delay, measurement Astro came second behind SvelteKit. Astro took the lead in Cumulative Layout Shift, Largest Contentful Paint (LCP), and Interaction to Next Paint (INP) measurements.

Integrate Astro with other front-end frameworks

Another advantage of Astro is its ability to seamlessly integrate with popular front-end frameworks and libraries such as React, Vue, and Svelte.

This compatibility ensures that developers can leverage their existing knowledge and expertise while harnessing the benefits of Astro.

Astro is fast by default

One of the strengths of Astro is its ability to deliver blazing-fast websites. By employing static site generation and intelligent caching, Astro optimizes performance by pre-rendering pages and serving them directly from a Content Delivery Network (CDN). This approach eliminates the need for server-side rendering on every request, resulting in lightning-fast loading times and improved SEO rankings.

Astro simplifies data fetching

Astro reduces the complexity associated with managing multiple data sources and APIs.

With its unified data fetching approach, you can fetch data from various sources and render them alongside static content. This simplifies the development process and eliminates the need for separate data fetching and rendering logic, resulting in cleaner code and improved developer productivity.

What is new in Astro V4?

The latest version of Astro, version 4.2, was released recently with new experimental features and bug fixes. To try out these new features, you can upgrade to the latest version using the @astrojs/upgrade command.

npx @astrojs/upgrade

You can also use your package manager's upgrade command like so:

npm install astro@latest
pnpm upgrade astro --latest
yarn upgrade astro --latest

Let's explore these features in the sub-sections below.

Experimental feature for prerendering pages using the Speculation Rules API

The new release of Astro introduces a new feature for prerendering pages using the browser's experimental Speculation Rules API. You can use it to prerender pages that a user is likely to visit next. This helps improve browsing experience. You can try out this experimental feature by adding the changes below to the astro.config.mjs file:

import { defineConfig } from "astro/config";

export default defineConfig({
prefetch: true,
experimental: {
clientPrerender: true,
},
});

Only chromium-based browsers support the Speculation Rules API at the moment. However, Astro provides fallback option for browsers that are yet to ship the Speculation Rules API.

Experimental feature modifying the routing priority for injected routes

Before the current version release, the routes you inject using the injectRoute API will have a higher priority than other routes. However, this behavior introduced issues that were difficult to debug.

With the new experimental feature, injected routes have the same priority order as other routes. You can enable this experimental feature by setting the value of the experimental.globalRoutePriority to true in the astro.config.mjs file.

import { defineConfig } from "astro/config";

export default defineConfig({
experimental: {
globalRoutePriority: true,
},
});

Feature to disable automatic redirects of the root URL to the default locale

In the version 4.2 release, you can disable Astro's default behavior of automatically redirecting the root URL to the default locale using the redirectToDefaultLocale config option.

import { defineConfig } from "astro/config";

export default defineConfig({
i18n: {
defaultLocale: "en",
locales: ["en", "fr"],
routing: {
prefixDefaultLocale: true,
redirectToDefaultLocale: false,
},
},
});

Automatic redirects of the root URL to the default locale is enabled by default.

Feature for customizing image optimization in Markdown

Before the version 4.2 release of Astro, images in Markdown files are optimized using Astro's default image optimization settings. However, with the 4.2 version release of Astro, you can customize how your Markdown images are optimized like so:

import { visit } from "unist-util-visit";

export default function remarkPlugin() {
return (tree) => {
visit(tree, "image", (node) => {
node.data.hProperties = node.data.hProperties || {};
node.data.hProperties.width = "100";
node.data.hProperties.height = "100";
});
};
}

This feature is still limited to only Markdown files. Hopefully, Astro will introduce support for other file types.

Improved accessibility rules

The Astro Dev Toolbar enhances your development experience by making it possible to inspect your web page and catch accessibility defects in development. The new release of Astro improves the accessibility rules in the Astro Dev Toolbar.

Fetching Data with GraphQL in Astro

Here is a short tutorial on how to integrate Astro with GraphQL. GraphQL is pretty useful for fetching structured data from web services. It works seamlessly with Astro in creating dynamic and content-driven websites.

Since Astro doesn't get in the way of using standard JavaScript to fetch data, integrating GraphQL is fairly uneventful. We can leverage any GraphQL client—most notably, apollo-client, graphql-request, or even just vanilla fetch—to create requests and pull data in.

But here's a very basic example with the graphql-request library, which is a reasonably lightweight way of fetching data from a GraphQL API.

Install graphql-request

First, install the graphql-request package:

npm install graphql-request

GraphQL API Data Fetching

So far in your Astro component, you can fetch data from a GraphQL endpoint. Following is an example of fetching posts from an imaginary GraphQL API:

---
import { request, gql } from 'graphql-request';

const query = gql`
query {
posts {
title
content
slug
}
}
`;

const { posts } = await request('https://example.com/graphql', query);
---

<article>
{posts.map((post) => (
<div key={post.slug}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</div>
))}
</article>

In this example:

  • The gql template literal is used to define the GraphQL query.
  • request() retrieves the data from the GraphQL API.
  • The data is then dynamically rendered inside the component.

Step 3: Handling Dynamic Queries

You can also make dynamic GraphQL queries based on parameters, like for example, a blog post slug. For example, to fetch a post having a specific URL slug:


import { request, gql } from "graphql-request";

const { slug } = Astro.params;

const query = gql`
query ($slug: String!) {
post(slug: $slug) {
title
content
}
}
`;

const { post } = await request('https://example.com/graphql', query, {
slug
});


<article>
<h1>{post.title}</h1>
<p>{post.content}
</article>

What we are doing here is passing the slug parameter in from the URL into the GraphQL query, so it fetches the right content dynamically.

Why Use GraphQL with Astro?

  • Efficient Data Fetching: GraphQL fetches only the needed data to make the payload size and performance better.
  • Flexible Queries: Given your requirement, you can exactly ask for what you need in one query without over-fetching or under-fetching your data.
  • Unified Data Source: You could fetch data from multiple sources with one GraphQL API, thus making it easier to manage content from several locations.

Tools for Simplifying GraphQL in Astro

You can also use more advanced GraphQL clients like Apollo Client for more complex needs—for example caching or subscriptions. Here is how you could set up Apollo Client in Astro:

  1. Apollo Client Installation:
npm install @apollo/client graphql
  1. Apollo Provider Setup: Now, we need to create an apollo.js file for configuring the Apollo Client:
import { ApolloClient, InMemoryCache } from "@apollo/client";

const client = new ApolloClient({
uri: "https://example.com/graphql",
cache: new InMemoryCache(),
});

export default client;
  1. Using Apollo in Components: It means you can now make use of the useQuery hook from Apollo in the components:
const { loading, error, data } = useQuery(GET_POSTS);

if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;

return (
<div>
{data.posts.map((post) => (
<div key={post.title}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</div>
))}
</div>
);

This will provide the groundwork for a more feature-filled integration, such as caching and support for multiple GraphQL queries within your Astro project.

Conclusion

Astro is a popular front-end framework. You can use it to build performant, content-rich websites such as blogs, landing pages, documentation sites, and portfolios.

You can create an Astro project instantly with the Astro command line tool or set up a project manually. The command line tool will instantly set up a complete Astro project with all the necessary setup.

Astro is a server-first framework by design. Astro Components don't render on the client side. They are built into plain HTML either at build time or on-demand during SSR.

You can opt-in to client-side rendering only when necessary by integrating Astro with popular front-end frameworks such as React, Vue, and Svelte.