从 Next.js 迁移
这里有一些关键的概念和迁移策略来帮助你开始。可以通过文档其他内容和我们的 Discord 社区来继续完成迁移!
Next.js 和 Astro 的主要相似之处
Section titled “Next.js 和 Astro 的主要相似之处”Next.js 和 Astro 有一些相似之处,可以帮助你迁移你的项目:
.astro
文件的语法类似于 JSX。编写 Astro 应该会感觉很熟悉。- Astro 项目也可以是 SSG 或具有页面预渲染功能的 SSR。
- Astro 使用基于文件的路由,并允许特定命名的页面创建动态路由。
- Astro 是基于组件的,标签在迁移前后会很相似。
- Astro 具有针对 React、Preact 和 Solid 的官方集成,因此可以使用现有的 JSX 组件。请注意,在 Astro 中,这些文件必须具有
.jsx
或.tsx
扩展名。 - Astro 支持安装 NPM 包,包括 React 库。许多现有的依赖将在 Astro 中起作用。
Next.js 和 Astro 的主要不同之处
Section titled “Next.js 和 Astro 的主要不同之处”当你在 Astro 中重建你的 Next.js 网站时,你会发现一些重要的区别:
-
Next.js 是一个 React 单页面应用,使用
index.js
作为项目的 root。Astro 是一个多页面的网站,index.astro
是你的主页。 -
.astro
组件:不是作为返回页面模板的导出函数去编写的。相反,会把你的代码分成一个 JavaScript 的”代码栅栏”和一个专门用于生成 HTML 的主体。 -
内容驱动:Astro 旨在展示你的内容,并允许你仅在需要时选择加入交互性。已有的 Next.js 应用程序可能是为重客户端交互性而构建的,并且包含一些使用
.astro
组件难以复制、更具挑战性的功能,可能需要高级 Astro 技术来处理,例如仪表盘。
转换你的 Next.js 项目
Section titled “转换你的 Next.js 项目”尽管每个项目在迁移的实际过程中都会有些不同,但在从 Next.js 转换到 Astro 时,有一些常见的操作可以参考。
创建一个新的 Astro 项目
Section titled “创建一个新的 Astro 项目”通过你的包管理器执行命令 create astro
来启动 Astro 的 CLI 向导,或从 Astro Theme Showcase 选择一个社区主题。
你可以在 create astro
命令中传递一个 --template
参数,用我们的官方启动器(如 docs
、blog
、portfolio
)启动一个新的 Astro 项目。或者,你可以从 GitHub 上任何现有的 Astro 仓库开始一个新项目。
# 启动 Astro CLI 安装向导npm create astro@latest
# 通过官方示例来创建一个新项目npm create astro@latest -- --template <example-name>
# 启动 Astro CLI 安装向导pnpm create astro@latest
# 通过官方示例来创建一个新项目pnpm create astro@latest --template <example-name>
# 启动 Astro CLI 安装向导yarn create astro@latest
# 通过官方示例来创建一个新项目yarn create astro@latest --template <example-name>
然后,把现有的 Next 项目文件复制到你的新 Astro 项目中,放在src
以外的单独文件夹中。
访问 https://astro.new 了解官方启动模板的完整列表,以及在 IDX、StackBlitz、CodeSandbox 或 Gitpod 中打开一个新项目的链接。
安装集成 (可选)
Section titled “安装集成 (可选)”在将 Next 项目转换为 Astro 时,你可能会发现安装一些 Astro 可选集成 是有用的。
-
@astrojs/react:在你的新 Astro 网站上重用一些现有的 React UI 组件,或者继续用 React 组件来编写。
-
@astrojs/mdx:将现有的 Next 项目中的 MDX 文件导入,或在新的 Astro 站点中使用 MDX。
将源码放在 src
目录内
Section titled “将源码放在 src 目录内”按照 Astro 的项目结构:
-
保持 Next 的
public/
文件夹不动。Astro 使用
public/
目录来存放静态资产,就像 Next 一样。这个文件夹和它的内容都不需要改变。 -
复制或移动 Next 的其他文件和文件夹(如
pages
,styles
等)到 Astro 的src/
文件夹,当你在重构网站时,遵循 Astro 的项目结构。像 Next 一样,Astro 的
src/pages/
文件夹是一个特殊的文件夹,用于基于文件的路由。所有其他文件夹都是可选的,你可以以任何方式组织你的src/
文件夹的内容。Astro 项目中其他常见的文件夹包括src/layouts/
、src/components
、src/styles
和src/scripts
。
Astro 的配置文件
Section titled “Astro 的配置文件”Astro 在你项目的根目录有一个配置文件,叫做 astro.config.mjs
。这仅用于配置你的 Astro 项目和任何已安装的集成,包括 SSR 适配器。
提示:将 JSX 文件转换为 .astro
文件
Section titled “提示:将 JSX 文件转换为 .astro 文件”这里有一些将 Next.js
组件转换为 .astro
组件的提示:
-
使用现有 Next.js 组件函数返回的 JSX 作为你的 HTML 模板的基础。
-
调整 Next 或 JSX 语法为 Astro 或 HTML 网页标准。例如,这包括
<Link>
、<Script>
、{children}
和className
。 -
将任何必要的 JavaScript,包括导入语句,移到”代码栅栏”(
---
)。注意:条件渲染内容的 JavaScript 通常直接写在 Astro 的 HTML 模板内。 -
使用
Astro.props
来访问之前传递给你的 Next 函数的任何额外 props。 -
决定是否需要将所有导入的组件都转换为 Astro。安装了官方集成后,你可以在 Astro 文件中使用现有的 React 组件。但是,如果它们不需要交互,你可能需要将它们转换为
.astro
组件! -
用导入语句或
import.meta.glob()
代替getStaticProps()
来查询你的本地文件。使用fetch()
来获取外部数据。
比较:JSX vs Astro
Section titled “比较:JSX vs Astro”比较以下 Next 组件和相应的 Astro 组件:
import Header from "./header";import Footer from "./footer";import "./layout.css";
export async function getStaticProps() { const res = await fetch("https://api.github.com/repos/withastro/astro"); const json = await res.json(); return { props: { message: json.message, stars: json.stargazers_count || 0 }, }}
const Component = ({ stars, message }) => {
return ( <> <Header /> <p style={{ backgroundColor: `#f4f4f4`, padding: `1em 1.5em`, textAlign: `center`, marginBottom: `1em` }}>Astro has {stars} 🧑🚀</p> <Footer /> </> )}
export default Component;
---import Header from "./header";import Footer from "./footer";import "./layout.css";
const res = await fetch("https://api.github.com/repos/withastro/astro");const json = await res.json();const message = json.message;const stars = json.stargazers_count || 0;---<Header /><p class="banner">Astro has {stars} 🧑🚀</p><Footer />
<style> .banner { background-color: #f4f4f4; padding: 1em 1.5em; text-align: center; margin-bottom: 1em; }<style>
迁移布局文件
Section titled “迁移布局文件”你可能会发现从把你的 Next.js 布局和模板转换为 Astro 布局组件开始会很有帮助。
Next 有两种不同的方法来创建布局文件,每种方法处理布局的方式都与 Astro 不同:
-
pages
目录
每个 Astro 页面都明确要求有 <html>
、<head>
和 <body>
标签,所以在不同的页面中重复使用一个布局文件是很常见的。Astro 使用一个 <slot />
作为页面内容,不需要导入语句。注意标准的 HTML 模板,以及对 <head>
的直接访问:
------<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> <!-- 用你现有的布局模板来包裹插槽元素 --> <slot /> </body></html>
迁移 Next.js 的 page
目录
Section titled “迁移 Next.js 的 page 目录”你的 Next 项目可能有一个 pages/_document.jsx
文件,它导入了一些 React 组件来定制你的应用程序的 <head>
:
import Document, { Html, Head, Main, NextScript } from "next/document";
export default class MyDocument extends Document { render() { return ( <Html lang="en"> <Head> <link rel="icon" href="/favicon.ico" /> </Head> <body> <Main /> <NextScript /> </body> </Html> ); }}
-
开发一个新的只返回 JSX 的 Astro 布局文件。
-
用
<html>
、<head>
、<slot>
和其他 HTML 标签替换 React 组件。。src/layouts/Document.astro <html lang="en"><head><link rel="icon" href="/favicon.ico" /></head><body><slot/></body></html>
迁移 Next.js 的 /app
目录
Section titled “迁移 Next.js 的 /app 目录”Next.js 的 app/
目录布局文件是用两个文件创建的:一个 layout.jsx
文件用于定制 <html>
和 <body>
内容,一个 head.jsx
文件用于定制 <head>
元素内容。
export default function Layout({ children }) { return ( <html lang="en"> <body>{children}</body> </html> );}
export default function Head() { return ( <> <title>My Page</title> </> );}
-
开发一个新的只返回 JSX 的 Astro 布局文件。
-
用一个单一的 Astro 布局文件替换这两个文件,其中包含一个页面外壳(
<html>
、<head>
和<body>
标签)和一个<slot/>
,而不是 React 的{children}
props:src/layouts/Layout.astro <html lang="en"><head><title>My Page</title></head><body><slot/></body></html>
迁移 Pages 和 Posts
Section titled “迁移 Pages 和 Posts”在 Next.js 中,你的文章要么在 /pages
,要么在 /app/routeName/page.jsx
。
在 Astro 中,除了使用 内容集合,所有的页面内容必须位于 src/
中。
React 页面
Section titled “React 页面”现有的 Next JSX (.js) 页面将需要从 JSX 文件转换为 .astro 页面。你不能在 Astro 中使用现有的 JSX 页面文件。
这些 .astro 页面必须位于 src/pages/
内,并将根据其文件路径自动生成页面路由。
Markdown 和 MDX 页面
Section titled “Markdown 和 MDX 页面”Astro 内置了对 Markdown 的支持和对 MDX 文件的可选整合。你可以复用任何现有的 Markdown 和 MDX 文件,但它们可能需要对它们的 frontmatter 进行一些调整,比如添加 Astro 的特殊 layout
frontmatter 属性。你将不再需要为每个 Markdown 生成的路由手动创建页面。这些文件可以放在 src/pages/
中,利用文件即路由的优势自动生成。
另外,你可以在 Astro 中使用内容集合来存储和管理你的内容。你将自己检索内容,并动态生成这些页面。
由于 Astro 输出的是原始 HTML,所以可以使用构建步骤的输出来编写端到端的测试。如果你已经能够匹配你的 Next 网站的标记,以前写的任何端到端测试都可能开箱即用。测试库,如 Jest 和 React 测试库,可以被导入并用于 Astro 测试你的 React 组件。
参见 Astro 的测试指南了解更多。
参考:将 Next.js 语法转换为 Astro 语法
Section titled “参考:将 Next.js 语法转换为 Astro 语法”将任何 Next <Link to="">
,<NavLink>
等组件转换成 HTML <a href="">
标签。
<Link to="/blog">Blog</Link><a href="/blog">Blog</a>
Astro 没有为链接使用任何特殊的组件,但我们欢迎你建立你自己的 <Link>
组件。然后你可以像使用其他组件一样导入和使用这个 <Link>
。
---const { to } = Astro.props;---<a href={to}><slot /></a>
更新任何文件导入以准确引用相对文件路径。这可以通过 import aliases 来完成,或者通过写出相对路径的全称。
请注意,“.astro “和其他一些文件类型必须以其完整的文件扩展名导入。
---import Card from "../../components/Card.astro";---<Card />
转换 Next 的 Children Props 到 Astro
Section titled “转换 Next 的 Children Props 到 Astro”将任何 {children}
的实例转换为 Astro 的 <slot />
。Astro 不需要接收 {children}
作为一个函数 prop,并会自动在 <slot />
中渲染 child 内容。
------export default function MyComponent(props) { return ( <div> {props.children} </div> );}
<div> <slot /></div>
可以使用命名插槽将有多个子组件的 React 组件迁移到 Astro 组件。
参考更多关于 Astro 中具体的 <slot />
用法。
转换 Next 的数据获取到 Astro
Section titled “转换 Next 的数据获取到 Astro”将 getStaticProps()
中任何实例转换为 import.meta.glob()
或 getCollection()
/getEntry()
,以便访问项目中其他文件的数据。为了获取远程数据,使用 fetch()
。
这些数据请求是在 Astro 组件的 frontmatter 中执行的,并使用 top-level await。
---import { getCollection } from 'astro:content';
// 获取所有 `src/content/blog/` 的入口const allBlogPosts = await getCollection('blog');
// 获取所有 `src/pages/posts/` 的入口const allPosts = Object.values(import.meta.glob('../pages/posts/*.md', { eager: true }));
const response = await fetch('https://randomuser.me/api/');const data = await response.json();const randomUser = data.results[0];---
查看更多关于用 import.meta.glob()
导入本地文件,使用 Collections API 查询或获取远程数据。
转换 Next 的样式到 Astro
Section titled “转换 Next 的样式到 Astro”你可能需要用 Astro 中其他可用的 CSS 选项来替换任何 CSS-in-JS 库(例如 styled-components)。
如果有必要,将任何内联样式对象(style={{ fontWeight: "bold" }}
)转换成内联 HTML 样式属性(style="font-weight:bold;"
)。或者,使用 Astro <style>
标签实现 scoped CSS styles。
<div style={{backgroundColor: `#f4f4f4`, padding: `1em`}}>{message}</div><div style="background-color: #f4f4f4; padding: 1em;">{message}</div>
在安装了 Tailwind Vite 插件之后,就支持 Tailwind。不需要对你现有的 Tailwind 代码进行任何修改!
查看更多关于在 Astro 中设计样式 的信息。
转换 Next 的 图像插件到 Astro
Section titled “转换 Next 的 图像插件到 Astro”根据情况在你的 React 组件中将任何 Next 的 <Image />
组件转换为 Astro 自己的图像集成组件或者转化成标准的 HTML <img>
/ JSX <img />
标签。
Astro 的 <Image />
组件只能在 .astro
和 .mdx
文件中工作。查看该组件可用的完整组件属性列表,并注意它与 Next 的属性有一些不同。
---import { Image } from 'astro:assets';import rocket from '../assets/rocket.png';---<Image src={rocket} alt="太空中的火箭飞船。" /><img src={rocket.src} alt="太空中的火箭飞船。">
在 React (.jsx
) 组件中,使用标准的 JSX 的图片语法(<img />
)。Astro 不会优化这些图片,但你也可以安装和使用 NPM 包以提升灵活性。
你可以在图像指南中了解更多关于在 Astro 中使用图像的信息。
示例:数据请求
Section titled “示例:数据请求”下面是一个 Next.js Pokédex 数据获取转换为 Astro 的例子。
pages/index.js
使用 REST PokéAPI 获取并显示前 151 个神奇宝贝的列表。
下面是如何在 src/pages/index.astro
中重新创建,将 getStaticProps()
替换为 fetch()
。
-
定义要返回的 JSX。
pages/index.js import Link from 'next/link'import styles from '../styles/poke-list.module.css';export default function Home({ pokemons }) {return (<><ul className={`plain-list ${styles.pokeList}`}>{pokemons.map((pokemon) => (<li className={styles.pokemonListItem} key={pokemon.name}><Link className={styles.pokemonContainer} as={`/pokemon/${pokemon.name}`} href="/pokemon/[name]"><p className={styles.pokemonId}>No. {pokemon.id}</p><img className={styles.pokemonImage} src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`} alt={`${pokemon.name} picture`}></img><h2 className={styles.pokemonName}>{pokemon.name}</h2></Link></li>))}</ul></>)}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,},}} -
创建
src/pages/index.astro
使用 Next 函数的返回值。将任何 Next 或 React 语法转换成 Astro,包括改变任何 HTML 全局属性的大小写。
注意:
-
.map
就已经可以了 -
className
变成class
。 -
<Link>
变成<a>
。 -
<> </>
在 Astro 模板设计中是不需要的。 -
key
是一个 React 属性而不是li
标签的一个属性。
src/pages/index.astro ------<ul class="plain-list pokeList">{pokemons.map((pokemon) => (<li class="pokemonListItem"><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> -
-
添加任何需要的 imports, props, 和 JavaScript
注意:
- 不再需要
getStaticProps
函数。来自 API 的数据直接在代码栅栏中被获取。 - 一个
<Layout>
组件被导入并包裹了页面模板。
src/pages/index.astro ---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 => {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}});---<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> - 不再需要
如果你找到(或制作)了一个关于将 Next.js 网站转换为 Astro 的有用的视频或博客文章,请将其 添加到这个列表中!