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:
- How to Create Your First Astro Project
- Astro Project Structure
- How to define Pages and Layouts in Astro
- How to Build Components with Astro
- How to add event handlers in Astro
- Working with Static and Dynamic Content in Astro
- How to Manage Data in Astro
- Why you should use Astro?
- What is new in Astro V4?
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:
- Apollo Client Installation:
npm install @apollo/client graphql
- 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;
- 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.