mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-09 21:07:49 +00:00
Revert "chore: apply Sarah's edits"
This reverts commit 6c08280fec451e0b4ec45f4840abc145916db271.
This commit is contained in:
committed by
Corbin Crutchley
parent
fee34d404c
commit
5880d687ec
@@ -10,21 +10,13 @@
|
||||
}
|
||||
---
|
||||
|
||||
## Guided example: Next data fetching to Astro
|
||||
## Guided examples: See the steps!
|
||||
|
||||
Here is an example of Next.js Pokédex data fetch converted to Astro.
|
||||
|
||||
This walks through converting three individual files to `.astro` files:
|
||||
|
||||
- `_document.js` for providing a layout becomes `src/layouts/Layout.astro`
|
||||
- `pages/index.js` Pokemon list page becomes `src/pages/index.astro`
|
||||
- `pages/pokemon/[name].js` for dynamic routing becomes `src/pages/[name].astro`
|
||||
|
||||
Note that `_app.js` is not needed in an Astro project.
|
||||
Here are examples of three files from Next's example templates converted to Astro.
|
||||
|
||||
### Next base layout to Astro
|
||||
|
||||
This example converts the main project layout (`/pages/_document.js`) to `src/layouts/Layout.astro`.
|
||||
This example converts the main project layout (`/pages/_document.js`) to `src/layouts/Layout.astro` which receives props from pages on your site.
|
||||
|
||||
1. Identify the return().
|
||||
|
||||
@@ -59,7 +51,7 @@ This example converts the main project layout (`/pages/_document.js`) to `src/la
|
||||
- `<Head>` becomes `<head>`
|
||||
- `<Main />` becomes `<slot />`
|
||||
- `className` becomes `class`
|
||||
- `<NextScript>` is not used in Astro.
|
||||
- We do not need `<NextScript>`
|
||||
|
||||
```astro
|
||||
---
|
||||
@@ -81,14 +73,25 @@ This example converts the main project layout (`/pages/_document.js`) to `src/la
|
||||
|
||||
3. Import the CSS (found in `_app.js`)
|
||||
|
||||
Next.js imports global styling via a CSS import in `_app.js`. This import is moved to Astro's layout component:
|
||||
In addition to the `_document` file, the NextJS application has a `_app.js` file that imports global styling via a CSS import:
|
||||
|
||||
```astro {0-3}
|
||||
```jsx {2}
|
||||
// pages/_app.js
|
||||
import '../styles/index.css'
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
```
|
||||
|
||||
This CSS import can be moved to the Astro Layout component:
|
||||
|
||||
```astro {1-4}
|
||||
---
|
||||
// src/layouts/Layout.astro
|
||||
import '../styles/index.css'
|
||||
---
|
||||
|
||||
|
||||
<html>
|
||||
<head lang="en">
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
@@ -102,22 +105,23 @@ This example converts the main project layout (`/pages/_document.js`) to `src/la
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
`pages/index.js` fetches and displays a list of the first 151 Pokémon using [the REST PokéAPI](https://pokeapi.co/).
|
||||
### Convert a Next.js `getStaticProps` Page to Astro
|
||||
|
||||
Here's how to recreate that in `src/pages/index.astro`, replacing `getStaticProps()` with `fetch()`.
|
||||
This is a page that lists the first 151 Pokémon using [the REST PokéAPI](https://pokeapi.co/).
|
||||
|
||||
1. Identify the return() JSX.
|
||||
|
||||
```jsx {6-18}
|
||||
```jsx
|
||||
// pages/index.js
|
||||
import Link from 'next/link'
|
||||
import Head from 'next/head'
|
||||
import styles from '../styles/poke-list.module.css';
|
||||
|
||||
export default function Home({ pokemons }) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Pokedex: Generation 1</title>
|
||||
</Head>
|
||||
<ul className={`plain-list ${styles.pokeList}`}>
|
||||
{pokemons.map((pokemon) => (
|
||||
<li className={styles.pokemonListItem} key={pokemon.name}>
|
||||
@@ -155,49 +159,132 @@ export const getStaticProps = async () => {
|
||||
}
|
||||
```
|
||||
|
||||
2. Create `src/pages/index.astro`
|
||||
#### Move Next Page Templating to Astro
|
||||
|
||||
Use the return value of the Next function. Convert any Next or React syntax to Astro, including changing the case of any [HTML global attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes).
|
||||
|
||||
Note that:
|
||||
|
||||
- `.map` just works!
|
||||
|
||||
- `className` becomes `class`.
|
||||
|
||||
- `<Link>` becomes `<a>`.
|
||||
|
||||
- The `<> </>` fragment is not required in Astro templating.
|
||||
To start migrating this page to Astro, start with the returned JSX and place it within an `.astro` file:
|
||||
|
||||
```astro
|
||||
---
|
||||
// src/pages/index.astro
|
||||
import styles from '../styles/poke-list.module.css';
|
||||
---
|
||||
<ul class="plain-list pokeList">
|
||||
|
||||
<head>
|
||||
<title>Pokedex: Generation 1</title>
|
||||
</head>
|
||||
<ul class={`plain-list ${styles.pokeList}`}>
|
||||
{pokemons.map((pokemon) => (
|
||||
<li class="pokemonListItem" key={pokemon.name}>
|
||||
<a class="pokemonContainer" href={`/pokemon/${pokemon.name}`}>
|
||||
<p class="pokemonId">No. {pokemon.id}</p>
|
||||
<img class="pokemonImage" src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`} alt={`${pokemon.name} picture`}/>
|
||||
<h2 class="pokemonName">{pokemon.name}</h2>
|
||||
</a>
|
||||
<li class={styles.pokemonListItem} key={pokemon.name}>
|
||||
<a class={styles.pokemonContainer} href={`/pokemon/${pokemon.name}`}>
|
||||
<p class={styles.pokemonId}>No. {pokemon.id}</p>
|
||||
<img class={styles.pokemonImage} src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`} alt={`${pokemon.name} picture`}></img>
|
||||
<h2 class={styles.pokemonName}>{pokemon.name}</h2>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
```
|
||||
|
||||
3. Add any needed imports, props and JavaScript
|
||||
During the migration to Astro templating, this example also:
|
||||
|
||||
Note that:
|
||||
- Imported styles move to the code fence
|
||||
- Removed the `<>` container fragment, as it is not needed in Astro's template.
|
||||
- Changed `className` to a more standard `class` attribute.
|
||||
- Migrated the Next `<Link>` component to an `<a>` HTML element.
|
||||
|
||||
- the `getStaticProps` function is no longer needed. Data from the API is fetched directly in the code fence.
|
||||
- A `<Layout>` component is imported, and wraps the page templating.
|
||||
Now move the `<head>` into your existing `layout.astro` file. To do this, we can:
|
||||
|
||||
```astro
|
||||
1. Pass the `title` property to the `layout.astro` file via `Astro.props`
|
||||
2. Import the layout file in `/src/pages/index.astro`
|
||||
3. Wrap the Astro page's template in the Layout component
|
||||
|
||||
```astro {5,11}
|
||||
---
|
||||
// src/layouts/Layout.astro
|
||||
import '../styles/index.css'
|
||||
|
||||
const {title} = Astro.props;
|
||||
---
|
||||
|
||||
<html>
|
||||
<head lang="en">
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="screen">
|
||||
<div class='screen-contents'>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```astro {4,7,19}
|
||||
---
|
||||
// src/pages/index.astro
|
||||
import styles from '../styles/poke-list.module.css';
|
||||
import Layout from '../layouts/layout.astro';
|
||||
---
|
||||
|
||||
<Layout title="Pokedex: Generation 1">
|
||||
<ul class={`plain-list ${styles.pokeList}`}>
|
||||
{pokemons.map((pokemon) => (
|
||||
<li class={styles.pokemonListItem} key={pokemon.name}>
|
||||
<a class={styles.pokemonContainer} href={`/pokemon/${pokemon.name}`}>
|
||||
<p class={styles.pokemonId}>No. {pokemon.id}</p>
|
||||
<img class={styles.pokemonImage} src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`} alt={`${pokemon.name} picture`}></img>
|
||||
<h2 class={styles.pokemonName}>{pokemon.name}</h2>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Layout>
|
||||
```
|
||||
|
||||
#### Move Next Page Logic Requests to Astro
|
||||
|
||||
This is the `getStaticProps` method from the NextJS page:
|
||||
|
||||
```jsx
|
||||
export const getStaticProps = async () => {
|
||||
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
|
||||
const resJson = await res.json();
|
||||
const pokemons = resJson.results.map(pokemon => {
|
||||
const name = pokemon.name;
|
||||
// https://pokeapi.co/api/v2/pokemon/1/
|
||||
const url = pokemon.url;
|
||||
const id = url.split("/")[url.split("/").length - 2];
|
||||
return {
|
||||
name,
|
||||
url,
|
||||
id
|
||||
}
|
||||
});
|
||||
return {
|
||||
props: {
|
||||
pokemons,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This then passes the `props` into the `Home` component that's been defined:
|
||||
|
||||
```jsx
|
||||
export default function Home({ pokemons }) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In Astro, this process is different. Instead of using a dedicated `getStaticProps` function, move the props logic into the code fence of our Astro page:
|
||||
|
||||
```astro {5-17}
|
||||
---
|
||||
// src/pages/index.astro
|
||||
import styles from '../styles/poke-list.module.css';
|
||||
import Layout from '../layouts/layout.astro';
|
||||
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151");
|
||||
const resJson = await res.json();
|
||||
const pokemons = resJson.results.map(pokemon => {
|
||||
@@ -213,35 +300,31 @@ const pokemons = resJson.results.map(pokemon => {
|
||||
});
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<ul class="plain-list pokeList">
|
||||
{pokemons.map((pokemon) => (
|
||||
<li class="pokemonListItem" key={pokemon.name}>
|
||||
<a class="pokemonContainer" href={`/pokemon/${pokemon.name}`}>
|
||||
<p class="pokemonId">No. {pokemon.id}</p>
|
||||
<img class="pokemonImage" src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`} alt={`${pokemon.name} picture`}/>
|
||||
<h2 class="pokemonName">{pokemon.name}</h2>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Layout title="Pokedex: Generation 1">
|
||||
<ul class={`plain-list ${styles.pokeList}`}>
|
||||
{pokemons.map((pokemon) => (
|
||||
<li class={styles.pokemonListItem} key={pokemon.name}>
|
||||
<a class={styles.pokemonContainer} href={`/pokemon/${pokemon.name}`}>
|
||||
<p class={styles.pokemonId}>No. {pokemon.id}</p>
|
||||
<img class={styles.pokemonImage} src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`} alt={`${pokemon.name} picture`}></img>
|
||||
<h2 class={styles.pokemonName}>{pokemon.name}</h2>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Layout>
|
||||
```
|
||||
|
||||
### Next dynamic routing to Astro
|
||||
You should now have a fully working Pokédex entries screen.
|
||||
|
||||
This is a Next.js dynamic routing page (`pages/pokemon/[name].js`) that generates a detail screen for each of the first 151 Pokémon using [the REST PokéAPI](https://pokeapi.co/).
|
||||
### Convert a Next.js `getStaticPaths` Page to Astro
|
||||
|
||||
Here's how to recreate that in `src/pages/pokemon/[name].astro`, also using `getStaticPaths()` in Astro.
|
||||
This is a Next.js dynamic page that generates a detail screen for each of the first 151 Pokémon using [the REST PokéAPI](https://pokeapi.co/).
|
||||
|
||||
**[tl/dr]:**
|
||||
1. Identify the return().
|
||||
2. Convert JSX to Astro by replacing Next or React syntax with Astro/HTML syntax.
|
||||
3. Add any needed JavaScript, props, imports.
|
||||
|
||||
```jsx {11-33}
|
||||
```jsx
|
||||
// pages/pokemon/[name].js
|
||||
import { useRouter } from 'next/router';
|
||||
import Head from 'next/head'
|
||||
import styles from '../../styles/pokemon-entry.module.css';
|
||||
|
||||
function capitalize(str) {
|
||||
@@ -250,8 +333,13 @@ function capitalize(str) {
|
||||
|
||||
export default function Pokemon({ pokemon }) {
|
||||
const router = useRouter();
|
||||
const title = `Pokedex: ${pokemon.name}`;
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
</Head>
|
||||
<button onClick={() => router.back()} className={styles.backBtn} aria-label="Go back"></button>
|
||||
<img className={styles.pokeImage} src={pokemon.image} alt={`${pokemon.name} picture`} />
|
||||
<div className={styles.infoContainer}>
|
||||
<h1 className={styles.header}>No. {pokemon.id}: {pokemon.name}</h1>
|
||||
@@ -312,16 +400,9 @@ export const getStaticProps = async (context) => {
|
||||
}
|
||||
```
|
||||
|
||||
#### Create `src/pages/pokemon/[name].astro`
|
||||
#### Move Next Page Templating to Astro
|
||||
|
||||
Use the return value of the Next function. Convert any Next or React syntax to Astro.
|
||||
|
||||
Note that:
|
||||
|
||||
- Imported styles are moved to the code fence.
|
||||
- `className` becomes `class`.
|
||||
- `<Layout>` is added to provide the page shell.
|
||||
- `{pokemon.*}` values just work!
|
||||
To start migrating this page to Astro, start with the returned JSX and place it within an `.astro` file:
|
||||
|
||||
```astro
|
||||
---
|
||||
@@ -329,7 +410,8 @@ Note that:
|
||||
import styles from '../../styles/pokemon-entry.module.css';
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Layout title={`Pokedex: ${pokemon.name}`}>
|
||||
<button onclick="history.go(-1)" class={styles.backBtn} aria-label="Go back"></button>
|
||||
<img class={styles.pokeImage} src={pokemon.image} alt={`${pokemon.name} picture`} />
|
||||
<div class={styles.infoContainer}>
|
||||
<h1 class={styles.header}>No. {pokemon.id}: {pokemon.name}</h1>
|
||||
@@ -353,16 +435,122 @@ import styles from '../../styles/pokemon-entry.module.css';
|
||||
</div>
|
||||
</Layout>
|
||||
```
|
||||
#### Add any needed imports, props and JavaScript
|
||||
|
||||
Like before:
|
||||
|
||||
1. Convert Next's `getStaticPaths()` function to Astro's `getStaticPaths()` by removing the `paths` route prefix and returning an array. (Otherwise, the functions are the same.) This function goes into Astro's code fence.
|
||||
- Imported styles are moved to the code fence.
|
||||
- `className` becomes `class`.
|
||||
- `<Head>` contents are moved into `<Layout>`.
|
||||
- `{pokemon.id}` values are interpolated the same as before.
|
||||
|
||||
However, in addition, now:
|
||||
|
||||
- [HTML's standard `onclick`](https://developer.mozilla.org/en-US/docs/Web/Events/Event_handlers#using_onevent_properties) function is used to call [the browser's `history.go` function](https://developer.mozilla.org/en-US/docs/Web/API/History/go) to navigate back.
|
||||
|
||||
#### Move Next `getStaticPaths` to Astro
|
||||
|
||||
Astro supports a function called `getStaticPaths` to generate dynamic paths, similar to Next.
|
||||
|
||||
Given a Next page:
|
||||
|
||||
```jsx
|
||||
// pages/pokemon/[name].js
|
||||
export const getStaticPaths = async () => {
|
||||
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
|
||||
const resJson = await res.json();
|
||||
const pokemons = resJson.results;
|
||||
|
||||
return {
|
||||
paths: pokemons.map(({ name }) => ({
|
||||
params: { name },
|
||||
}))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Migrate the `getStaticPaths` method to Astro by removing the `paths` route prefix and returning an array:
|
||||
|
||||
```astro {10-12}
|
||||
---
|
||||
// src/pages/pokemon/[name].astro
|
||||
import styles from '../../styles/pokemon-entry.module.css';
|
||||
|
||||
export const getStaticPaths = async () => {
|
||||
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
|
||||
const resJson = await res.json();
|
||||
const pokemons = resJson.results;
|
||||
|
||||
return pokemons.map(({ name }) => ({
|
||||
params: { name },
|
||||
}))
|
||||
}
|
||||
---
|
||||
|
||||
<Layout title={`Pokedex: ${pokemon.name}`}>
|
||||
<button onclick="history.go(-1)" class={styles.backBtn} aria-label="Go back"></button>
|
||||
<img class={styles.pokeImage} src={pokemon.image} alt={`${pokemon.name} picture`} />
|
||||
<div class={styles.infoContainer}>
|
||||
<h1 class={styles.header}>No. {pokemon.id}: {pokemon.name}</h1>
|
||||
<table class={styles.pokeInfo}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Types</th>
|
||||
<td>{pokemon.types}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Height</th>
|
||||
<td>{pokemon.height}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Weight</th>
|
||||
<td>{pokemon.weight}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class={styles.flavor}>{pokemon.flavorText}</p>
|
||||
</div>
|
||||
</Layout>
|
||||
```
|
||||
|
||||
Then, similar to the previous page, migrate the `getStaticProps` method to non-function-wrapped code in the Astro page's code fence.
|
||||
|
||||
Given the Next page logic:
|
||||
```jsx
|
||||
// pages/pokemon/[name].js
|
||||
function capitalize(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
export const getStaticProps = async (context) => {
|
||||
const { name } = context.params
|
||||
const [pokemon, species] = await Promise.all([
|
||||
fetch(`https://pokeapi.co/api/v2/pokemon/${name}`).then(res => res.json()),
|
||||
fetch(`https://pokeapi.co/api/v2/pokemon-species/${name}`).then(res => res.json())
|
||||
])
|
||||
|
||||
return {
|
||||
props: {
|
||||
pokemon: {
|
||||
id: pokemon.id,
|
||||
image: pokemon.sprites.front_default,
|
||||
name: capitalize(pokemon.name),
|
||||
height: pokemon.height,
|
||||
weight: pokemon.weight,
|
||||
flavorText: species.flavor_text_entries[0].flavor_text,
|
||||
types: pokemon.types.map(({ type }) => type.name).join(', ')
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Migrate this to the Astro page's code fence:
|
||||
|
||||
:::tip
|
||||
Use `Astro.props` to access the `params` returned from the `getStaticPaths` function.
|
||||
Use `Astro.props` to access the `params` returned from the `getStaticPaths` function
|
||||
:::
|
||||
|
||||
```astro {4-11, 14}
|
||||
```astro {15-33}
|
||||
---
|
||||
// src/pages/pokemon/[name].astro
|
||||
import styles from '../../styles/pokemon-entry.module.css';
|
||||
@@ -377,59 +565,11 @@ export const getStaticPaths = async () => {
|
||||
}))
|
||||
}
|
||||
|
||||
const { name } = Astro.props;
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<img class={styles.pokeImage} src={pokemon.image} alt={`${pokemon.name} picture`} />
|
||||
<div class={styles.infoContainer}>
|
||||
<h1 class={styles.header}>No. {pokemon.id}: {pokemon.name}</h1>
|
||||
<table class={styles.pokeInfo}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Types</th>
|
||||
<td>{pokemon.types}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Height</th>
|
||||
<td>{pokemon.height}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Weight</th>
|
||||
<td>{pokemon.weight}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class={styles.flavor}>{pokemon.flavorText}</p>
|
||||
</div>
|
||||
</Layout>
|
||||
```
|
||||
|
||||
2. Remove the `getStaticProps()` method wrapping the API data fetch, as in the previous example. Add this, along with all other necessary JavaScript for the templating, to the Astro page code fence.
|
||||
|
||||
|
||||
|
||||
```astro {16-33}
|
||||
---
|
||||
// src/pages/pokemon/[name].astro
|
||||
import styles from '../../styles/pokemon-entry.module.css';
|
||||
|
||||
export const getStaticPaths = async () => {
|
||||
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
|
||||
const resJson = await res.json();
|
||||
const pokemons = resJson.results;
|
||||
|
||||
return pokemons.map(({ name }) => ({
|
||||
params: { name },
|
||||
}))
|
||||
}
|
||||
|
||||
const { name } = Astro.props;
|
||||
|
||||
function capitalize(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
const { name } = Astro.props;
|
||||
const [pokemonData, species] = await Promise.all([
|
||||
fetch(`https://pokeapi.co/api/v2/pokemon/${name}`).then(res => res.json()),
|
||||
fetch(`https://pokeapi.co/api/v2/pokemon-species/${name}`).then(res => res.json())
|
||||
@@ -446,7 +586,8 @@ const pokemon = {
|
||||
};
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Layout title={`Pokedex: ${pokemon.name}`}>
|
||||
<button onclick="history.go(-1)" class={styles.backBtn} aria-label="Go back"></button>
|
||||
<img class={styles.pokeImage} src={pokemon.image} alt={`${pokemon.name} picture`} />
|
||||
<div class={styles.infoContainer}>
|
||||
<h1 class={styles.header}>No. {pokemon.id}: {pokemon.name}</h1>
|
||||
@@ -469,4 +610,6 @@ const pokemon = {
|
||||
<p class={styles.flavor}>{pokemon.flavorText}</p>
|
||||
</div>
|
||||
</Layout>
|
||||
```
|
||||
```
|
||||
|
||||
You have now fully migrated a Pokédex application from Next to Astro.
|
||||
Reference in New Issue
Block a user