Here I go through how to set up a blog using Astro and how to integrate SSR into an Astro application.
Usually used for static websites. It includes SSR support, so you can add different frameworks into it such as React. This is one the biggest advantages of using Astro. There might be a few pages on a website that need to be static and other pages where you have an SSR need such as fetching a data end-point.
mkdir [dir-name]
npm init astro
Go through the options in the CLI, selecting the options which best suit the needs of the application you are building.
astro dev
Depending on selections made when setting up the project, will depend on the structure.
If you select the recommended you should have the follow:
node_modules
public
favicon.svg
src
components
Card.astro
layouts
Layout.astro
pages
index.astro
env.d.ts
.gitignore
astro.config.mjs
package.json
package-lock.json
README.md
tsconfig.json
This is the easiest way to work with Markdown within Astro and helps you oragnise content, validate frontmatter and provide automatic Typescript type-safety.
A content collection is any directory inside the reserved src/content
.
// content.config.ts
import { defineCollection } from "astro:content";
export const collections = {
blog: defineCollection({
schema: z.object({
date: z.date()
categories: z.array(z.string())
title: z.string()
})
}),
};
Create a folder within content called blog
and add a simple markdown file to test the above.
blog/first-post.md
---
date: 01-01-01
categories: [test]
title: Hello!
---
# Hello World
Zod deals with the shape of the data and schema validations, it’s a run-time check. It will throw errors if the types given isn’t correct.
Like many other frameworks, pages handle routing, data loading and overall page layout of every page.
These are the supported file types:
// index.astro
---
import { getCollection } from 'astro:content';
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
const blogs = await getCollection('')
---
<Layout title="Welcome to Astro.">
<ul role="list" class="link-card-grid">
{blogs.map((blog) => {
return(
<Card
href={`/blog/${blog.slug}`}
title={blog.data.title}
body={blog.data.summary}
/>
)
})}
</ul>
</Layout>
The page of blog post doesn’t exist at the moment. Create a blog
directory within blog
and then create a file called [slug].astro
.
// [slug].astro
---
import { getEntryBySlug } from 'astro:content'
import Layout from '../../layouts/Layout.astro'
export async function getStaticPaths() {
const allPosts = await getCollection('blog');
return allPosts.map((post) => {
return {
params: { slug: post.slug },
props: { post },
}
})
}
const { post } = Astro.props as { post: CollectionEntry<'blog'> };
const { Conent } = await post.render();
---
<Layout title={post.data.title}>
<h1>{post.data.title}</h1>
<p>Category: {post.data.categories.join(', ')}</p>
<Content />
</Layout>
Each file within src/pages/
becomes an endpoint on your site, based on the file path. A single page can generate multiple pages via dynamic routing.
If you want to link between pages you can use the <a>
tag.
End-points export a GET
which receives a content object with properties similar to Astro
global.
Here is an example of using params
and dynamic routing.
// src/pages/[id].json.ts
import type { APIRoute } form 'astro';
const usernames = ["one", "two", "three"];
export const get: APIroute = ({ params, request }) => {
const id = params.id;
return {
body: JSON.stringify({
name: usernames[id]
})
}
}
export functon getStaticPaths() {
return [
{ params: { id: "0" } },
{ params: { id: "1" } },
{ params: { id: "2" } },
]
}
All end-points recieve a request
property but in static mode you only have access to request.url
.
// src/pages/request-path.json.ts
export const get: APIRoute = ({ params, request }) => {
return {
body: JSON.stringify({
path: new URL(request.url).pathname,
}),
};
};
// src/components/User.astro
---
import Contact from '../components/Contact.jsx';
import Location form '../components/Location.astro';
const response = await fetch('https://randomuser.me/api/');
const data = await response.json();
const randomUser = data.results[0];
---
<!-- Data fetched at build can be rendered in HTML -->
<h1>User</h1>
<h2>{randomUser.name.first} {randomUser.name.last}</h2>
<!-- Data fetched at build can be passed to components as props-->
<Contact client:load email={randomUser.email} />
<Location city={randomUser.location.city} />
Astro allows you to intergrate many different SSR frameworks. The example below is for React however you can mix components from different frameworks.
npx astro add react
npm install @astro/react
If you get the error cannot find package ‘react’ (or similar), do the following:
npm install react react-dom
Apply the integration to the astro.config.*
file:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react'
export default defineConfig({
// ...
integrations: [react()]
})
// src/pages/static-components.astro
---
import MyComponent from '../components/MyComponent.jsx';
---
<html>
<body>
<h1>Using React with Astro</h1>
<MyComponent />
</body>
</html>
A framework component can be made interactive (hydrated) using a client:*
directive.
// src/pages/interactive-components.astro
---
import InteractiveButton from '../components/InteractiveButton.jsx';
import InteractiveCounter from '../components/InteractiveCounter.jsx';
import InteractiveModal from '../components/InteractiveModal.svelte';
---
<!-- This component's js will begin importing when the page loads -->
<InteractiveButton client:load />
<!-- This component's js will not be sent to the client until the user scrolls down the component is visible on the page-->
<InteractiveCounter client:visible />
<!-- This component won't render on the server but will render on the client when the page loads -->
<InteractiveModal client:only="svelte">