Astro is the newest static site generator on the scene. And, it’s cool! I’m excited about Astro because of how it views its role as a framework. It seems to have a balanced approach to the opinions and abilities it offers.
Rather than offering super strong opinions (think: Next.js’s page routing, Gatsby’s GraphQL layer), Astro takes many of the popular approaches to common patterns and provides a foundation on which developers can build. Instead of saying, “Do it yourself,” Astro says, “Here are some common options today. Pick one (or more).”
You can read more about why I’m so excited. Also, take a look at Astro’s announcement if you haven’t read it yet. It’s some great background on the tool.
Astro is new to the scene, but there are already a handful of tutorials on it. Let’s not make this yet another one. Instead, let’s explore the cool parts of what makes Astro so exciting by going through an example.
We’re going to take the default starting point for Astro projects and turn it into a simple one-page website that lists some fun facts about rockets! Why? Because Astro and LogRocket go together like … avocado and toast?
WARNING! Not Production Ready
You’re at the forefront of the Astro journey — it’s not technically ready for production yet. That’s likely still a ways out, but it’s solid enough to explore. And if you like it enough, sure, live on the edge and take the thing to production.
Begin by getting this thing set up. Make a new directory for your project and navigate to it:
mkdir astro-the-cool-parts cd astro-the-cool-parts
Then you can start a new Astro project:
npm init astro
This will walk you through a few questions. Here are the answers I gave:
y
to confirmStarter Kit (Generic)
as the starting pointReact
and Svelte
for my componentsOnce you’re through that step, install the dependencies:
npm install
Then you can start the server:
npm start
Now, visit localhost:3000
in your web browser to show the default starting point.
I recommend exploring the contents of this project. It provides a good base for showing what Astro can do. However, we’re going to rip most of that out for our example.
The pages for Astro projects are in the src/pages
directory. The homepage is src/pages/index.astro
. I removed most of the default content and replaced it with fun facts and a few inline styles to support. Here’s what my file looks like.
Now we’re going to build the project and inspect what happens. Run the build:
npm run build
The output files are in the dist
directory. Notice there’s no JavaScript!
Let’s add a component to represent a fun fact. Create a new file at src/components/FunFact.jsx
with the following content:
import styles from "./FunFact.module.css"; export default function FunFact({ children, heading, sourceUrl }) { return ( <div className={styles.wrapper}> <h2 className={styles.heading}>{heading}</h2> <p className={styles.fact}>{children}</p> <p> <a href={sourceUrl}>Source</a> </p> </div> ); }
Note: this is a React component. You’re welcome to use a different framework. Change the syntax, file extensions, and imports as needed.
Notice that we imported a file for styles (using CSS Modules) that doesn’t exist yet. Let’s create it now at src/components/FunFact.module.css
and drop in these styles:
.wrapper { margin-bottom: 2rem; } .heading { margin-bottom: 0.5rem; font-size: 1.4rem; } .fact { font-size: 1rem; line-height: 1.5; margin-bottom: 0.5rem; } .source { font-size: 0.75rem; }
Next, let’s rearrange index.astro
to use the components.
At the top of the file, import our new component:
import FunFact from '../components/FunFact.jsx' // ...
Then adjust the main body content to use the <FunFact />
component:
<main> <FunFact heading="Rockets can reach speeds of 22,000 miles per hour!" sourceUrl="https://www.ulalaunch.com/explore/rocket-science/fun-facts"> A typical rocket produces more than a million pounds of thrust that allows it to carry more than 6,000 pounds at speeds topping 22,000 miles per hour. This is equivalent to the power generated by 13 Hoover Dams, carrying the weight of eight horses, and traveling at speeds 15 times faster than a speeding bullet! </FunFact> <FunFact heading="Prior to 1926, all rockets were powered by some form of gunpowder." sourceUrl="https://www.factsjustforkids.com/technology-facts/rocket-facts-for-kids/"> The first rocket to use liquid fuel was created by Robert H. Goddard and first launched on March 16th, 1926. </FunFact> <FunFact heading="The first rocket in space was launched by Germany in 1942." sourceUrl="https://facts.net/science/technology/rocket-facts/"> Germany launched the first rocket capable of reaching space in 1942. Dubbed the V-2 rocket, it was not actually intended for space travel. Instead, the V-2 was constructed as a ballistic missile during WWII. Nonetheless, it was revealed in a flight test to be the first man-made object to fly into space. </FunFact> <FunFact heading="The first rocket was invented in China around 1100 AD" sourceUrl="https://facts.net/science/technology/rocket-facts/"> The rockets invented in the 10th century China used solid propellants and were mainly used as weapons and fireworks. It was not until the 1920s that rocket science was studied further. By the 1930s and 1940s, professional rocket engineering started to take off. </FunFact> </main>
Here you can see all the changes I made during this step.
When your browser looks good, run the build again (npm run build
) and take a look at the dist
directory.
Notice how minimal the changes were, and, more importantly, there’s still no JavaScript, even though we’ve included a React component!
As a quick example, let’s say that another developer is more comfortable with Svelte, so they are going to build the header using Svelte.
Add a new component at src/components/Header.svelte
with the following code from the home page:
<header> <div> <h1>🚀 Rocket Fun Facts 🚀</h1> </div> </header> <style> header { display: flex; flex-direction: column; gap: 1em; margin: 0 auto 3rem; max-width: min(100%, 68ch); } </style>
Now, in index.astro
, you can import the new header:
import Header from '../components/Header.svelte'
And use it in the homepage markup:
<head> <!-- ... --> <style> main { margin: 0 auto; max-width: 42rem; } </style> </head> <body> <Header /> <!-- ... --> </body>
Quick note: header styles are hanging out in public/style/home.css
, which you could (and should) clean up if you were taking this to production. I’m leaving them where they are because this is a quick proof of concept.
If everything went well, there should be no changes to the result on the screen, as you’ve only done some reorganization.
Here are the changes I made, which again resulted in only minor build changes, leaving the dist
directory nice and clean.
I love the way Astro approaches data fetching right at the top of component files. It even supports top-level await
, which works to reduce the amount of boilerplate code you have to write.
I didn’t want to do anything too fancy here because we’re just playing around, so I dropped all the content into a JSON file at content/fun-facts.json
. It looks like this:
[ { "heading": "Rockets can reach speeds of 22,000 miles per hour!", "sourceUrl": "https://www.ulalaunch.com/explore/rocket-science/fun-facts", "body": "A typical rocket produces more than a million pounds of thrust that allows it to carry more than 6,000 pounds at speeds topping 22,000 miles per hour. This is equivalent to the power generated by 13 Hoover Dams, carrying the weight of eight horses, and traveling at speeds 15 times faster than a speeding bullet!" }, { "heading": "Prior to 1926, all rockets were powered by some form of gunpowder.", "sourceUrl": "https://www.factsjustforkids.com/technology-facts/rocket-facts-for-kids/", "body": "The first rocket to use liquid fuel was created by Robert H. Goddard and first launched on March 16th, 1926." }, { "heading": "The first rocket in space was launched by Germany in 1942.", "sourceUrl": "https://facts.net/science/technology/rocket-facts/", "body": "Germany launched the first rocket capable of reaching space in 1942. Dubbed the V-2 rocket, it was not actually intended for space travel. Instead, the V-2 was constructed as a ballistic missile during WWII. Nonetheless, it was revealed in a flight test to be the first man-made object to fly into space." }, { "heading": "The first rocket was invented in China around 1100 AD", "sourceUrl": "https://facts.net/science/technology/rocket-facts/", "body": "The rockets invented in the 10th century China used solid propellants and were mainly used as weapons and fireworks. It was not until the 1920s that rocket science was studied further. By the 1930s and 1940s, professional rocket engineering started to take off." } ]
Then I committed and pushed the code, and generated a URL to that file using this service. This will make it feel like we’re fetching data from an API. You can follow the same process or just use the URL I generated.
Let’s begin by only fetching the content and logging the result to the console. Adjust the code at the top of your index.astro
file:
const dataUrl = 'https://raw.githack.com/seancdavis/astro-the-cool-parts/77d3b5dd2ce2253c33d50fc91a21875f90a8ced5/content/fun-facts.json' const response = await fetch(dataUrl); const facts = await response.json(); console.log(facts);
Now, restart the server. Notice that the content is logged to the server and not in the browser. That’s because this code is run during the Astro build process, not when the page is loaded.
Handling iterable data in Astro
Astro components don’t have logic baked into the markup portion. So, rather than looping through the data returned from our fake API, we’ll create another component to handle the looping.
Add the component to src/components/FunFactList.jsx
, like so:
import FunFact from "./FunFact"; export default function FunFactList({ facts }) { return ( <> {facts.map((fact, idx) => ( <FunFact key={idx} heading={fact.heading} sourceUrl={fact.sourceUrl}> {fact.body} </FunFact> ))} </> ); }
Notice all this does is take an array of data, loop through it, and generate individual <FunFact />
components.
Back in the index.astro
file, change import
to use the FunFactList
component, not the FunFact
component.
import FunFactList from '../components/FunFactList.jsx'
Then remove the console.log
and replace your existing fun facts with the single FunFactList
component:
<FunFactList facts={facts} />
Here are the component changes, and when building, there’s still no JavaScript! And we’re bringing in dynamic data!
Of all the features Astro offers, progressive enhancement (or partial hydration) is perhaps the coolest. We can selectively tell Astro when to hydrate components to make them interactive.
Let’s say we want to collapse the fun facts by default and show them when clicking on the heading. We’ll also toggle an emoji to indicate when a particular fact is opened or closed.
Adjust the code in the fun fact component:
import { useState } from "react"; import styles from "./FunFact.module.css"; export default function FunFact({ children, heading, sourceUrl }) { const [expanded, setExpanded] = useState(false); let wrapperClasses = styles.wrapper; if (expanded) wrapperClasses += ` ${styles.wrapperExpanded}`; return ( <div className={wrapperClasses}> <h2 className={styles.heading}> <button onClick={() => setExpanded(!expanded)}> <span>{expanded ? "🟣" : "⚪️"}</span> <span>{heading}</span> </button> </h2> <p className={styles.fact}>{children}</p> <p> <a href={sourceUrl}>Source</a> </p> </div> ); }
Let’s add a bit more styling:
.wrapper { margin-bottom: 2rem; } .wrapperExpanded .fact { display: block; } .heading { margin-bottom: 0.5rem; font-size: 1.4rem; } .heading button { background-color: inherit; border: inherit; color: inherit; display: inherit; font-size: inherit; line-height: inherit; margin: inherit; padding: inherit; text-align: inherit; } .heading button:hover { cursor: pointer; } .heading button span:first-child { display: inline-block; margin-right: 0.5rem; } .fact { display: none; font-size: 1rem; line-height: 1.5; margin-bottom: 0.5rem; } .source { font-size: 0.75rem; }
Here’s the commit with these changes.
Now load the homepage in the browser. The styles are there, but nothing works. What the heck?
That’s because Astro’s interactivity is declarative. You have to opt into it using one of its client directives. Adjust the index.astro
to add the client:visible
directive when rendering the FunFactList
component.
<FunFactList facts={facts} client:visible />
Reload the page and everything should now be working. Here is the commit.
There are a few different directives that can be used, and it’s all about timing. In this case, we used client:visible
, which will make the component interactive only after it enters the viewport.
Now take a look at the dist
directory. There’s a lot more going on, including some JavaScript files.
That was a quick exploration of the parts of Astro that I’m most excited about. Of course, there’s plenty more to explore, and Astro will continue to evolve. But there is a place for Astro in this already crowded ecosystem of static site generators, and I’m excited to put it into practice on my projects.
There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.
LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently — start monitoring for free.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.