Your First Steps with the Astro Framework
Tags: astro, beginners, javascript, programming
Published: 2022-12-30 06:10:23

For a few years, web projects have been taking more and more responsibility, which means more and more JavaScript.

Nowadays, the top SPA Frameworks, React, Vue, or Angular, have great developer experience and allow us to build complex applications but have some pain points.

*   The size of JavaScript to Browsers.
    
*   The SEO doesn't like the SPA.
    

Each framework tries to solve the problem with bundle strategies, split in modules, lazy loading, and SSR to try to patch the problem. The hydration process is so heavy for the browser, so our issues still need to be solved 100%.

### What can we do?

The web is continuously evolving, and some solutions exist to help us with performance and SEO, and today I will talk about [Astro](https://astro.build/).

## What is Astro?

Astro is a “static” website generator focusing on performance and SEO, providing interactivity with Astro components or using the power from React Vue or Svelte.

Astro helps us move away from heavy JavaScript Websites but keep the power of use components scalable and maintainable websites.

Astro works with MPA and Island architecture, the old MPA frameworks use server language, but Astro works with Javascript, which allows rendering components on the server and client side.

The Island Architecture means Astro works with small apps or islands to add interactivity, using Astro components or working as a container to add React, Vue, or Solid components.

The Island architecture is not unique to Astro. Other frameworks are, but with other approaches:

*   Eleventy with Preact: Using the combination of Eleventy and Preact with the`WithHydration` wrapper to hydrate the components on the client.
    
*   Marko: it ships the interactive component's hydration code to change the browser's state, so the Marko compiler optimizes where it runs, client or server.
    

We already have an overview of Astro, but the best way to learn is by building something from zero.

## The Project

We will build an app and consume the Rick And Morty API. The app shows a list of characters on the home page, click on details and go to the details page when clicking on a single character.

We must create an Astro project with the following:

*   Components: Header, Navigation, Footer, Character List, Character
    
*   Pages: Home and CharacterDetail.
    
*   API: Functions to provide the data.
    

### Create The Project

Using the terminal, run the following command `npm create astro@latest`. It will trigger an assistant to ask questions and pick `Just the basics` . Use Typescript strictly to set everything ready to start.

```bash
npm create astro@latest
npx: installed 78 in 8.962s
+—————+  Houston:
| ^ u ^  I'll be your assistant today.
+—————+
 astro   v1.6.12 Launch sequence initiated.

? Where would you like to create your new project? » astro_rick_morty
```

> The option `Just the basic` creates an essential structure for us. It helps us to explain some points about Astro.

### Project Structure

We have the project; let's navigate into the project to see every directory:

*   public: to store static files like images.
    
*   pages: All files in the pages are components and work as routes.
    
*   layouts: The components work as containers to reuse sections on all pages.
    
*   components: store all components to use in the app.
    
*   `astro.config`: help us to define other frameworks to use in the app (if we need them)
    

Delete the generated files `Card.astro` , `Layout.astro` and `index.astro`, to start from zero.

Create the `index.astro` file again, run the terminal command, and start the local server in port 3000 with an empty page in the browser.

```bash
astro dev
   astro  v1.6.12 started in 36ms
  ┃ Local    http://127.0.0.1:3000/
  ┃ Network  use --host to expose
```

## The Astro File

Before creating the components, I want to give an overview of the Astro Components sections, the `ComponentScript` and `ComponentTemplate`.

### ComponentScript:

It is the Typescript area defined with `---` a front matter like markdown files and allows import of Astro or framework components declares variables, and functions to use in the `ComponentTemplate`.

```typescript
---
const hello = (name: string) => {
	return `Hello${name}!`
}
const title = 'Welcome Astro'
---
```

### Component Template

It renders HTML or imported components and supports Javascript expression and Astro directives.

The Astro Component syntax is a superset of HTML (Like Typescript for Javascript), allowing us JSX-like expression but is not JSX.

```html
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
		<meta name="viewport" content="width=device-width" />
		<meta name="generator" content={Astro.generator} />
		<title>Astro</title>
	</head>
	<body>
		<h1>{title}</h1>
		{ hello('Bezael')}
	</body>
</html>
```

We have already finished an overview of Astro components. Let's build our components.

> Read more about components in Astro.
> 
> *   [Differences Between Astro Syntax and JSX](https://docs.astro.build/en/core-concepts/astro-components/#differences-between-astro-and-jsx)
>     
> *   More About [Astro Directives](https://docs.astro.build/en/reference/directives-reference/)
>     

### Add Tailwind

We want some app styling to easily integrate Astro with a single command and configure everything automatically.

```bash
npx astro add tailwind
```

Read [Tailwind integration](https://docs.astro.build/es/guides/integrations-guide/tailwind/) in Astro docs.

## Components

We will start with two simple components the `footer` and `navigation`, we learn each component has two main areas.

The footer: create a `footer.astro` file in the `components` directory, and add `ComponentScript` area with `---` and create the variable `message` with the `Build with Love and Astro`. In the template, add a message variable using the `{}`:

The final code looks like this:

```html
---
const message = 'Build with Love and Astro'
---
<footer>
    {message}
</footer>
```

The navigation: Similar to the footer, create `navigation.astro` and declare an array n the `ComponentScript` area. The object for navigation is an array, and the object has two values `path` and `title`.

```typescript
const options = [{ path: '/', title: 'Home'}, { path: 'about', title: 'About'}];
```

Use the map function in the `ComponentTemplate` using a JSX-like expression. Add the link element to render the menu properties inside the map. `{value}`.

> Note: I'm using some tailwind classes just styling.

```markdown
---
const options = [{ path: '/', title: 'Home'}, { path: 'about', title: 'About'}];
---
<div
class="flex flex-col items-center justify-center space-y-3 md:flex-row md:space-y-0 md:space-x-8  md:justify-end"
> 
        {options.map((p: any) => 
         <div class="group">
            <a href={p.path}>{p.title}</a>
            <div
              class="mx-2 mt-2 duration-500 border-b-2 opacity-0 border-black group-hover:opacity-100"
            ></div>
        </div>
        )}
</div>
```

## Layout

The Layouts are components to provide reusable UI; they work as a container for the pages combined with the to give space to inject the pages. (It works like the router-outlet in Angular).

In the layouts, the directory creates the `layout.astro` file; in the ComponentScript, import `Footer` and `Navigation` components.

We added the Navigation and Footer and a slot to Astro inject the pages.

The final code looks like this:

```typescript
---
import Footer from "../components/footer.astro";
import Navigation from "../components/navigation.astro";
---
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
		<meta name="viewport" content="width=device-width" />
		<meta name="generator" content={Astro.generator} />
		<title>Astro</title>
	</head>
	<body>
        <Navigation></Navigation>
        <div class="flex flex-col items-center justify-center min-h-screen m-2 bg-cyan-50">
                       
            <slot>
            
            </slot>
            <Footer></Footer>

        </div>

</body>
</html>
```

> Read more about [slot](https://docs.astro.build/en/core-concepts/astro-components/#slots) and [Layout](https://docs.astro.build/en/core-concepts/layouts/)

## Pages

The pages are components in the `pages/` responsible for managing the routing, load data, and page content.

The `index.astro` component is the first page; we want to reuse the layout component and import the `Layout` component.

Add h1 with `Hello Astro` in the Layout body, Astro renders the content in the slot area defined in the layout.

The final code looks like this:

```html
---
import Layout from "../layouts/layout.astro";
---
<Layout>
<h1>
    Hello from Astro
    </h1>
</Layout>
```

Save changes, the `Hello from Astro` the message is shown with the layout style.


![Hello from Astro](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4fx1x8jgnplooabf36k.png)

## Get Data From API

The `CharacterList` and `Character` need data to bind to the components to interact, so we are going first to create the functions to get the data

Because I'm an Angular guy, I created the directory `services` with the file `rickmorty.service.ts`.

The service has five functions:

*   getAPICharacters: Get the list of all characters from API.
    
*   getAPISingleCharacter: Get a single character from ID.
    
*   transformData: transform the response and return some properties.
    
*   getSingleCharacterDetail: return the Character from ID.
    
*   getCharacters: return all Characters.
    

Because the `rickandmorty` API has a structure; it creates an interface for each entity.

```typescript

export type APICharacterResponse = {
    results: APICharacterModel[]
}

export interface APICharacterModel {
    id:       number;
    name:     string;
    status:   string;
    species:  string;
    type:     string;
    gender:   string;
    origin:   Location;
    location: Location;
    image:    string;
    episode:  any[];
    url:      string;
    created:  Date;
}

export interface Location {
    name: string;
    url:  string;
}
```

### API Calls

I won't go into deep details, but We use the `async` await and fetch to get the data.

```typescript
const getAPICharacters = async (): Promise<APICharacterResponse> => {
    const charactersRequest = await fetch(API);
    return charactersRequest.json();
}

const getAPISingleCharacter = async (id: string) => {
    const request = await fetch(`${API}/${id}`);
    return request.json();
}
```

Because the API Response is a complex object, we create the `transformData` method.

```typescript
const transformData = (characters: APICharacterResponse) => {
    return characters.results.map((c) => {

        return {
            id: c.id,
            name: c.name,
            image: c.image
        }
    });

}
```

Finally, create two functions from the components `getCharacters` and `getSingleCharacterDetail`. Each one calls a specific method to get the data from API.

```typescript
export const getCharacters = async () => {
    const characters = await getAPICharacters();
    return transformData(characters);
}


export const getSingleCharacterDetail = async (id: string) => {

    return await getAPISingleCharacter(id);

}
```

> Learn more about [async, await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function), and [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)

## Astro.props And Components

We need to create two components and pass data to it:

*   character: show a character image and name.
    
*   characterlist: take the responsibility to list all characters.
    

To pass the data to the components, we use `Astro.props` to read values from component attributes, attributes like:

```html
<my-component title="hi" id=""/>
```

Create the `character.astro` file in the script section, extract to attributes for the component: `title` and `image` from `Astro.props`

In the Markup, use the variables as we did before:

The final code looks like this:

```html
---
const { name, image } =  Astro.props;
---
<div class="relative group">
    <img  alt={name} width="100%" height="100%"  src={image} />
    <div
    class="absolute bottom-0 left-0 right-0 p-2 px-4 text-white duration-500 bg-black opacity-0 group-hover:opacity-100 bg-opacity-40"
  >
    <div class="flex justify-between w-full">
      <div class="font-normal">
        <p class="text-sm">{name}</p>
      </div>
    </div>
  </div>
</div>
```

Next, we have to work with the `CharacterList` , which uses the Character components, gets a list of characters from the API, and iterates over the array similar to the `Navigation`.

First, import the Character component, and from `Astro.props` get the `character`

```typescript
---
import Character from "./character.astro";
const { characters } = Astro.props;
---
```

In the Component template, iterate over the character array and render the component.

The final code looks like this:

```typescript
---
import Character from "./character.astro";
const { characters } = Astro.props;
---

<div class="grid gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
  {
    characters.map((character: any) => (
        <Character name={character.name} image={character.image}></Character>
    ))
  }
</div>
```

### Display the Data

We are in the final steps; in this step, we want to show the list of Characters and get the data from API.

Open the `index.astro` and import the CharacterList component and the getCharacter function from `rickymorty.service`

Define the `characters` variable to save the data from the `getCharacter` function using the await keyword. In the template, pass the characters to the `CharacterList` component

The final code looks like this:

```html
---
import Characterlist from "../components/characterlist.astro";
import Layout from "../layouts/layout.astro";
import { getCharacters } from "../services/rickmorty.service";

const characters = await getCharacters();
---
<Layout>
<Characterlist characters={characters}></Characterlist>
</Layout>
```

Save and see the list of the Characters in the browser.

Perfect! We have the list, but I want to click on one character and see the details.


![list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmctx7btcxbocbht0ysv.png)

### Passing Parameters

Astro supports static and dynamic parameters and dynamic routes using the file name like characters/\[\].astro and reading it using Astro.params

For the dynamic parameters, we must use the `server()` mode, by default, uses static. To change it, open the `astro.config.mjs` add the output in the `defineConfig` section to the server:

```typescript
export default defineConfig({
  output: 'server',
  integrations: [tailwind()]
});
```

Next, create a new file in the pages section, like `characters/[id].astro`

Import the Character component and the `getSingleCharacterDetail` function.

Get the id from `Astro.params` and pass it to the `getSingleCharacterDetail`, and using await, get the response and extract the name and image.

The \[id\].astro final code looks like this:

```html
---

import Character from "../../components/character.astro";
import Layout from "../../layouts/layout.astro";
import { getSingleCharacterDetail } from "../../services/rickmorty.service";

const { id = '' } = Astro.params;
const  {name, image}  =  await getSingleCharacterDetail(id);
---
<Layout>
<Character name={name} image={image}></Character>
</Layout>
```

To allow the user to click in detail and navigate. Edit the `characterlist.astro` component and wrap the component with an `<a` in the `href` pass the route `characters/${character.id}`

The final code looks like this:

```html
---
import Character from "./character.astro";
const { characters } = Astro.props;
---

<div class="grid gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
  {
    characters.map((character: any) => (
      <a href={`characters/${character.id}`}>
        <Character name={character.name} image={character.image}></Character>
      </a>
    ))
  }
</div>
```

Save and navigate to the character details!


![final](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7n84iibg01h2mn2747x7.gif)

## Deploy

We already created a basic app with Astro, but we still need the deployment, and Astro has an easy way to deploy and excellent [documentation](https://docs.astro.build/en/guides/deploy/). My example is using `vercel`

The Astro team has `Adapters` to deploy quickly and configure the `astro.config.mjs` with a single command:

```bash
npx astro add vercel
```

> Read more [about deploy](https://docs.astro.build/en/guides/deploy/vercel/)

## Recap

We learn a bit about how astro works, and how easy it is to build an app. Astro looks like a great alternative for building static websites using the power of frameworks like React, Vue, or Svelte if we require extra reactivity.

Feel free to see the web or play with the GitHub code.

*   [https://github.com/danywalls/demo\_astro](https://github.com/danywalls/demo_astro)
    
*   [https://demo-astro-psi.vercel.app/](https://demo-astro-psi.vercel.app/)