Astro describes itself as an ‘all-in-one web framework for building fast, content-focused websites’. This post details the steps to create a blog using Astro which are mostly taken from the official Astro tutorial but also touches on various Astro integrations that make life easier.
Setup
Install node and vscode.
Install create-astro:
1
2
3
4
5
cd Code/Astro
npm create astro@latest
...
cd blog-tutorial
code .
Follow the prompts to choose empty project, select y to install dependejncies, y to init a new git repo and choose relaxed for typescript option.
After opening in vscode, install the astro language extension:
Astro - Visual Studio Marketplace
Run the dev server:
1
npm run dev
Navigate to http://localhost:3000/e
Push to git:
1
2
3
git remote add origin git@github.com:trex0r/astro-blog.git
git branch -M main
git push -u origin main
Deploy to netlify by logging in, clicking add new site, import an existing project, connect to github, grant permission to see the repo you created above, and then click deploy site. On the site’s overview, you’ll see the website URL. You can change the project name to something more memorable to update the URL. Every time you push to the repo, netlify will rebuild your site.
Pages
Astro pages
Create a new page called about.astro and blog.astro in the src/pages directory and add some html. Note that astro uses standard a tags for linking between different routes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
---
---
<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>
<a href="/">Home</a>
<a href="/about/">About</a>
<a href="/blog/">Blog</a>
<h1>My Astro Learning Blog</h1>
<p>This is where I will post about my journey learning Astro.</p>
</body>
</html>
Markdown blog posts
Create src/pages/posts and a md file within called post-1.md. You can navigate to it at localhost:3000/posts/post-1
Note the frontmatter:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
---
title: 'My First Blog Post'
pubDate: 2022-07-01
description: 'This is the first post of my new Astro blog.'
author: 'Astro Learner'
image:
url: 'https://astro.build/assets/blog/astro-1-release-update/cover.jpeg'
alt: 'The Astro logo with the word One.'
tags: ["astro", "blogging", "learning in public"]
---
# My First Blog Post
Published on: 2022-07-01
Welcome to my _new blog_ about learning Astro! Here, I will share my learning journey as I build a new website.
## What I've accomplished
1. **Installing Astro**: First, I created a new Astro project and set up my online accounts.
2. **Making Pages**: I then learned how to make pages by creating new `.astro` files and placing them in the `src/pages/` folder.
3. **Making Blog Posts**: This is my first blog post! I now have Astro pages and Markdown posts!
## What's next
I will finish the Astro tutorial, and then keep adding more posts. Watch this space for more to come.
The md will be automatically converted to html.
Note that you can use code fences like this:
1
2
3
4
5
6
```json
{
"firstName": "John",
"lastName": "Smith",
"age": 25
}
If you need to customise the look you can override the ‘astro-code’ class in csss.
Add dynamic content about you
You can define variables between the — — at the top of each astro file and then access them inside { } in the html. As shown in the example below, you can write javascript expressions inside the { }.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
---
const pageTitle = "About Me";
const identity = {
firstName: "Sarah",
country: "Canada",
occupation: "Technical Writer",
hobbies: ["photography", "birdwatching", "baseball"],
};
const skills = ["HTML", "CSS", "JavaScript", "React", "Astro", "Writing Docs"];
---
<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>{pageTitle}</title>
</head>
<body>
<a href="/">Home</a>
<a href="/about/">About</a>
<a href="/blog/">Blog</a>
<h1>{pageTitle}</h1>
<h2>... and my new Astro site!</h2>
<p>Here are a few facts about me:</p>
<ul>
<li>My name is {identity.firstName}.</li>
<li>
I live in {identity.country} and I work as a {identity.occupation}.
</li>
{
identity.hobbies.length >= 2 && (
<li>
Two of my hobbies are: {identity.hobbies[0]} and{" "}
{identity.hobbies[1]}
</li>
)
}
</ul>
<p>My skills are:</p>
<ul>
{skills.map((skill) => <li>{skill}</li>)}
</ul>
</body>
</html>
Conditionally rendering elements
You can use the && syntax to conditionally render elements. If the first statement is true, then whatever gets displayed after && is shown. You can also use the ternay operator in the formation expression ? shown if true : show if false.
1
2
3
4
5
6
7
8
9
10
11
12
13
---
const happy = true;
const finished = false;
const goal = 3;
---
...
{happy && <p>I am happy to be learning Astro!</p>}
{finished && <p>I finished this tutorial!</p>}
{goal === 3 ? <p>My goal is to finish in 3 days.</p> : <p>My goal is not 3 days.</p>}
Adding styling
You can add styling to individual pages in the style tags on each .astro file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html lang="en">
<head>
<meta charset ="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>{pageTitle}</title>
<style>
h1 {
color: purple;
font-size: 4rem;
}
.skill {
color: green;
font-weight: bold;
}
</style>
</head>
...
You can also add css variables as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
---
...
const skillColor = "navy";
const fontWeight = "bold";
const textCase = "uppercase";
---
<html lang="en">
<head>
...
<style define:vars=>
h1 {
color: purple;
font-size: 4rem;
}
.skill {
color: var(--skillColor);
font-weight: var(--fontWeight);
text-transform: var(--textCase);
}
</style>
</head>
<body>
...
<ul>
{skills.map((skill) => <li class="skill">{skill}</li>)}
</ul>
</body>
</html>
Adding a global stylesheet
The style tag is scope by default in astro. To create a global stylesheet, create src/styles/global.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
html {
background-color: #f1f5f9;
font-family: sans-serif;
}
body {
margin: 0 auto;
width: 100%;
max-width: 80ch;
padding: 1rem;
line-height: 1.5;
}
* {
box-sizing: border-box;
}
h1 {
margin: 1rem 0;
font-size: 2.5rem;
}
Then in about.astro add the import global css statement into the frontmatter:
1
2
3
4
---
import '../styles/global.css';
...
---
If there are conflicting global and local styles, then local styles override the global ones.
Astro components
Reusable Navigation component
Create /src/components/Navigation.astro
1
2
3
4
5
---
---
<a href="/">Home</a>
<a href="/about/">About</a>
<a href="/blog/">Blog</a>
You can use it on other pages as follows:
1
2
3
4
5
6
7
8
---
import Navigation from '../components/Navigation.astro';
---
...
<body>
<Navigation />
...
Passing props into a component
You can add in props to make a component more reusable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Social.astro
---
const { platform, username } = Astro.props;
---
<a href={`https://www.${platform}.com${username}`}>{platform}</a>
// Footer.astro
---
const platform = "github";
const username = "withastro";
import Social from "./Social.astro";
---
<footer>
<Social platform="twitter" username="astrodotbuild" />
<Social platform="github" username="withastro" />
<Social platform="youtube" username="astrodotbuild" />
</footer>
Adding a script to the browser
We can add a hamburger menu to close links on mobile screens, which requires some client side interactivity.
1
2
3
4
5
6
7
8
9
// index.astro
<Footer />
<script>
document.querySelector('.hamburger').addEventListener('click', () => {
document.querySelector('.nav-links').classList.toggle('expanded');
});
</script>
</body>
Or for an even simpler test, just console log something out:
1
2
3
4
5
<Footer />
<script>
console.log('hello world');
</script>
</body>
Importing a script from a .js file
Create src/scripts/menu.js
1
2
3
document.querySelector('.hamburger').addEventListener('click', () => {
document.querySelector('.nav-links').classList.toggle('expanded');
});
And then you can use it as follows:
1
2
3
4
5
6
7
8
9
10
11
....
<body>
<Header />
<h1>My Astro Site</h1>
<Footer />
<script>
import "../scripts/menu.js";
</script>
</body>
</html>
Layouts
Create /src/layouts/BaseLayout.astro. Notice the slot.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
---
import "../styles/global.css";
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
const pageTitle = "My Blog";
---
<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>{pageTitle}</title>
</head>
<body>
<Header />
<h1>My Astro Site</h1>
<slot />
<Footer />
<script>
import "../scripts/menu.js";
</script>
</body>
</html>
You can import it on other pages. Whatever you put within the BaseLayout tag is added in the slot in the layout above.
1
2
3
4
5
6
7
8
9
10
11
---
import "../styles/global.css";
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
import BaseLayout from "../layouts/BaseLayout.astro";
const pageTitle = "My Blog";
---
<BaseLayout>
<h2>My awesome blog subtitle</h2>
</BaseLayout>
Passing page-specific values as props
Pass in the prop:
1
2
3
4
5
6
7
---
import BaseLayout from '../layouts/BaseLayout.astro';
const pageTitle = "Home Page";
---
<BaseLayout pageTitle={pageTitle}>
<h2>My awesome blog subtitle</h2>
</BaseLayout>
And accept the prop as follows in the layout astro file:
1
2
3
---
const { pageTitle } = Astro.props;
---
Custom blog layout
Create a layout called src/layouts/MarkdownPostLayout.astro. Note that the frontmatter (what is between — — at the front of md files) is accessible.
1
2
3
4
5
6
---
const { frontmatter } = Astro.props;
---
<h1>{frontmatter.title}</h1>
<p>Written by {frontmatter.author}</p>
<slot />
You can then specify that this layout should be used in each md file:
1
2
3
4
5
6
7
8
9
10
11
---
layout: ../../layouts/MarkdownPostLayout.astro
title: 'My First Blog Post'
pubDate: 2022-07-01
description: 'This is the first post of my new Astro blog.'
author: 'Astro Learner'
image:
url: 'https://astro.build/assets/blog/astro-1-release-update/cover.jpeg'
alt: 'The Astro logo with the word One.'
tags: ["astro", "blogging", "learning in public"]
---
Nesting layouts
If you have multiple layouts, you can nest them together.
1
2
3
4
5
6
7
8
9
10
11
12
---
import BaseLayout from './BaseLayout.astro';
const { frontmatter } = Astro.props;
---
<BaseLayout pageTitle={frontmatter.title}>
<h1>{frontmatter.title}</h1>
<p>{frontmatter.pubDate.slice(0,10)}</p>
<p><em>{frontmatter.description}</em></p>
<p>Written by: {frontmatter.author}</p>
<img src={frontmatter.postImage} width="300" />
<slot />
</BaseLayout>
Server Side Rendering
Using nodejs
By default, astro deals with static site generation. You can enable SSR though, which I would recommend to avoid needing to keep deploying… I think it would be perfect when coupled with Directus.
Instructions here:
https://docs.astro.build/en/guides/server-side-rendering/
In summary (for nodejs):
1
npm install @astrojs/node
Edit astro.config.js to be as follows:
1
2
3
4
5
6
7
8
9
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone'
}),
});
You can then run it as normal with npm run dev for hot reloading.
For production, you can build as follows and then run it:
1
2
npm run build
node ./dist/server/entry.mjs
Note that by running that, the server will launch on 3000. You should do this at the same time as you do npm run dev.
Dynamic routing
Create /src/pages/editions/[id].astro
You can now access the id in the url (and run a graphql request to populate the data) as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
---
const { id } = Astro.params;
const response = await fetch("https://dev.dr.heni.com/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer xxx",
},
body: JSON.stringify({
query: `
query MyQuery {
Editions_by_id(id: "${id}") {
id
...
}
`,
}),
});
const json = await response.json();
const edition = json.data.Editions_by_id;
if (!edition) return Astro.redirect("/404");
---
<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" />
</head>
<body>
<h1>{`${edition.code} ${edition.name}`}</h1>
<h2>{edition.artist.name}</h2>
<p>Release date: {edition.release_date}</p>
<p>Framed: {edition.framed}</p>
<p>Edition Type: {edition.edition_type}</p>
{edition.charity && <p>charity</p>}
<p>Description: {edition.short_description}</p>
<img src={`https://resources.heni.com/${edition.image.id}__aabdc22667ffe5c19da90089ff13cb79ccd36dee.webp`} alt="">
{edition.gallery_of_images.map(x => (<img src={`https://resources.heni.com/${x.directus_files_id.id}__aabdc22667ffe5c19da90089ff13cb79ccd36dee.webp`} alt="">))}
</body>
</html>
Integrations
Tailwind
You can add Tailwind with this integration.
1
npx astro add tailwind
MDX
This allows you to bring in components into your markdown, and also provides an easy way of overriding the styling for certain components created in the markdown (such as h1s etc).
Add MDX to the project
1
npx astro add mdx
Create a component that we will use in our MDX.
src/components/mdx
1
2
3
4
5
---
const { buttonText } = Astro.props;
---
<button type="button">{buttonText}</button>
Create a markdown page. Note that you can define your yaml frontmatter as you would normally do with a markdown page. Note that you need to import your components outside of the frontmatter.
src/pages/test-markdown.mdx (navigate to localhost:3000/test-markdown)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
title: 'My first MDX post'
---
{/* Import components */}
import Button from "../components/Button.astro";
# {frontmatter.title}
Hello there, this is a test.
- this is another test
- and another one
1. a first example
2. a second
<Button buttonText="Please click on me!" />
Variables in MDX
The frontmatter is just for the yaml md definition. You can’t add in variables or perform fetching like you normally would. To do this, we do it inside the mdx itself:
1
2
3
4
5
6
7
---
title: 'My first MDX post'
---
export const test_var = "hello there cowboy";
# {test_var}
Overriding components
You can tie your md components to custom astro components easily. See here for what can be overridden:
https://mdxjs.com/table-of-components/
Let’s say that on one of our mdx pages, we are using a blockquote:
1
2
3
4
import Blockquote from '../components/Blockquote.astro';
export const components = {blockquote: Blockquote}
> This quote will be a custom Blockquote
To override >, look at the link above and find what html component it maps to.
Create a component (it doesn’t matter what name it has, but it makes sense to keep it similar to the html component you will override).
src/components/Blockquote.astro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
const props = Astro.props;
---
<blockquote {...props} class="blockquote_styling">
<span>"</span>
<slot />
<span>"</span>
</blockquote>
<style>
.blockquote_styling {
color: blue;
}
</style>
Here’s another example (nothing new, just with a different component). This time we will override an h1.
1
2
3
4
import Markdown_h1 from '../components/Markdown_h1.astro';
export const components = {blockquote: Blockquote, h1: Markdown_h1};
# Hello World
components/Markdown_h1.astro
1
2
3
4
5
6
7
8
9
10
11
12
13
---
const props = Astro.props;
---
<h1 {...props} class="md-h1">
<slot />
</h1>
<style>
.md-h1 {
color: green;
}
</style>
Astro Remote
1
npm install astro-remote
Use like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
---
import { Markup, Markdown } from "astro-remote";
import CodeBlock from "../components/CodeBlock.astro";
const response = await fetch("http://127.0.0.1:8055/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query MyQuery {
blog_articles {
id
md
}
}`,
}),
});
const json = await response.json();
const blog_article = json.data.blog_articles[0]['md'];
---
<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} />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/dark.min.css">
<title>Astro</title>
</head>
<body>
<h1>Astro</h1>
<Markdown content={blog_article} components=/>
</body>
</html>
Custom codeblocks using highlight js.
1
npm install highlight.js
Import and use as follows:
1
2
3
4
5
6
7
8
---
import hljs from 'highlight.js';
const { lang, code, ...props } = Astro.props;
const highlighted = hljs.highlight(code, {language: lang}).value
---
<pre class={`language-${lang}`}><code set:html={highlighted} /></pre>
Also need to import stylesheet
Can just add into the head in the index.astro file (you can pick different themes from here).
1
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/styles/default.min.css">
https://highlightjs.org/usage/#importing-the-library
Custom components:
/src/components/Button.astro
1
2
3
4
5
---
const { buttonText } = Astro.props;
---
<button type="button">{buttonText}</button>
And we can use it as follows in the md:
1
2
3
4
5
6
7
# this is a test
Hello world.
This is a link to [google](https://google.com).
<Button buttonText="Please click on me!" />
And render it as follows:
1
<Markdown content={blog_article} sanitize= components=/>
Compression
To make the css and js smaller, you can use this integration: https://github.com/astro-community/astro-compress#readme
To install:
1
npx astro add astro-compress
Pre fetch
You can use the astro pre fetch plugin to speed up your website significantly.
1
npx astro add prefetch
Then just add this to each a link:
1
rel="prefetch"
Svelte
You can use Svelete components within astro by doing the following:
1
npx astro add svelte
Create a svelete component called /components/Hello.svelte
1
2
3
4
5
<script>
let name = 'world';
</script>
<h1>Hello {name}!</h1>
And you can now load it from your .astro file:
1
2
3
4
5
6
7
8
---
import Hello from '../components/Hello.svelte';
---
<div>
<Hello />
</div>
If you have any interactivity in your Svelte comopnent, then you need to add in client:load:
1
<Hello client:load />