跳转到内容

从 Next.js 迁移

这里有一些关键的概念和迁移策略来帮助你开始。可以通过文档其他内容和我们的 Discord 社区来继续完成迁移!

Next.js 和 Astro 的主要相似之处

段落标题 Next.js 和 Astro 的主要相似之处

Next.js 和 Astro 有一些相似之处,可以帮助你迁移你的项目:

Next.js 和 Astro 的主要不同之处

段落标题 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 项目

段落标题 转换你的 Next.js 项目

尽管每个项目在迁移的实际过程中都会有些不同,但在从 Next.js 转换到 Astro 时,有一些常见的操作可以参考。

创建一个新的 Astro 项目

段落标题 创建一个新的 Astro 项目

通过你的包管理器执行命令 create astro 来启动 Astro 的 CLI 向导,或从 Astro Theme Showcase 选择一个社区主题。

你可以在 create astro 命令中传递一个 --template 参数,用我们的官方启动器(如 docsblogportfolio)启动一个新的 Astro 项目。或者,你可以从 GitHub 上任何现有的 Astro 仓库开始一个新项目

终端窗口
# 启动 Astro CLI 安装向导
npm create astro@latest
# 通过官方示例来创建一个新项目
npm create astro@latest -- --template <example-name>

然后,把现有的 Next 项目文件复制到你的新 Astro 项目中,放在src以外的单独文件夹中。

在将 Next 项目转换为 Astro 时,你可能会发现安装一些 Astro 可选集成 是有用的。

  • @astrojs/react:在你的新 Astro 网站上重用一些现有的 React UI 组件,或者继续用 React 组件来编写。

  • @astrojs/mdx:将现有的 Next 项目中的 MDX 文件导入,或在新的 Astro 站点中使用 MDX。

将源码放在 src 目录内

段落标题 将源码放在 src 目录内

按照 Astro 的项目结构

  1. 保持 Next 的 public/ 文件夹不动。

    Astro 使用 public/ 目录来存放静态资产,就像 Next 一样。这个文件夹和它的内容都不需要改变。

  2. 复制或移动 Next 的其他文件和文件夹(如 pagesstyles 等)到 Astro 的 src/ 文件夹,当你在重构网站时,遵循 Astro 的项目结构

    像 Next 一样,Astro 的 src/pages/ 文件夹是一个特殊的文件夹,用于基于文件的路由。所有其他文件夹都是可选的,你可以以任何方式组织你的 src/ 文件夹的内容。Astro 项目中其他常见的文件夹包括 src/layouts/src/componentssrc/stylessrc/scripts

Astro 在你项目的根目录有一个配置文件,叫做 astro.config.mjs。这仅用于配置你的 Astro 项目和任何已安装的集成,包括 SSR 适配器

提示:将 JSX 文件转换为 .astro 文件

段落标题 提示:将 JSX 文件转换为 .astro 文件

这里有一些将 Next.js 组件转换为 .astro 组件的提示:

  1. 使用现有 Next.js 组件函数返回的 JSX 作为你的 HTML 模板的基础。

  2. 调整 Next 或 JSX 语法为 Astro 或 HTML 网页标准。例如,这包括 <Link><Script>{children}className

  3. 将任何必要的 JavaScript,包括导入语句,移到“代码栅栏”(---。注意:条件渲染内容的 JavaScript 通常直接写在 Astro 的 HTML 模板内。

  4. 使用 Astro.props 来访问之前传递给你的 Next 函数的任何额外 props。

  5. 决定是否需要将所有导入的组件都转换为 Astro。安装了官方集成后,你可以在 Astro 文件中使用现有的 React 组件。但是,如果它们不需要交互,你可能需要将它们转换为 .astro 组件!

  6. 用导入语句或 Astro.glob() 代替 getStaticProps() 来查询你的本地文件。使用 fetch() 来获取外部数据。

参考 Next.js 文件逐步转换的例子

比较以下 Next 组件和相应的 Astro 组件:

StarCount.jsx
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;

你可能会发现从把你的 Next.js 布局和模板转换为 Astro 布局组件开始会很有帮助。

Next 有两种不同的方法来创建布局文件,每种方法处理布局的方式都与 Astro 不同:

每个 Astro 页面都明确要求有 <html><head><body> 标签,所以在不同的页面中重复使用一个布局文件是很常见的。Astro 使用一个 <slot /> 作为页面内容,不需要导入语句。注意标准的 HTML 模板,以及对 <head> 的直接访问:

src/layouts/Layout.astro
---
---
<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 目录

段落标题 迁移 Next.js 的 page 目录

你的 Next 项目可能有一个 pages/_document.jsx 文件,它导入了一些 React 组件来定制你的应用程序的 <head>

pages/_document.jsx
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>
);
}
}
  1. 开发一个新的只返回 JSX 的 Astro 布局文件。

  2. <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 目录

段落标题 迁移 Next.js 的 /app 目录

Next.js 的 app/ 目录布局文件是用两个文件创建的:一个 layout.jsx 文件用于定制 <html><body> 内容,一个 head.jsx 文件用于定制 <head> 元素内容。

app/layout.jsx
export default function Layout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
app/head.jsx
export default function Head() {
return (
<>
<title>My Page</title>
</>
);
}
  1. 开发一个新的只返回 JSX 的 Astro 布局文件。

  2. 用一个单一的 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>

在 Next.js 中,你的文章要么在 /pages,要么在 /app/routeName/page.jsx

在 Astro 中,你所有的页面内容必须在 src/ 中,在 src/pagessrc/content 其中。

现有的 Next JSX (.js) 页面将需要从 JSX 文件转换为 .astro 页面。你不能在 Astro 中使用现有的 JSX 页面文件。

这些 .astro 页面必须位于 src/pages/ 内,并将根据其文件路径自动生成页面路由。

Astro 内置了对 Markdown 的支持和对 MDX 文件的可选整合。你可以复用任何现有的 Markdown 和 MDX 文件,但它们可能需要对它们的 frontmatter 进行一些调整,比如添加 Astro 的特殊 layout frontmatter 属性。你将不再需要为每个 Markdown 生成的路由手动创建页面。这些文件可以放在 src/pages/ 中,利用文件即路由的优势自动生成。

另外,你可以在 Astro 中使用内容集合来存储和管理你的内容。当作为一个集合的一部分时,Markdown 和 MDX 文件将在 src/content/ 的文件夹中。你将自己检索内容,并动态生成这些页面

由于 Astro 输出的是原始 HTML,所以可以使用构建步骤的输出来编写端到端的测试。如果你已经能够匹配你的 Next 网站的标记,以前写的任何端到端测试都可能开箱即用。测试库,如 Jest 和 React 测试库,可以被导入并用于 Astro 测试你的 React 组件。

参见 Astro 的测试指南了解更多。

参考:将 Next.js 语法转换为 Astro 语法

段落标题 参考:将 Next.js 语法转换为 Astro 语法

将任何 Next <Link to=""><NavLink> 等组件转换成 HTML <a href=""> 标签。

<Link to="/blog">Blog</Link>
<a href="/blog">Blog</a>

Astro 没有为链接使用任何特殊的组件,但我们欢迎你建立你自己的 <Link> 组件。然后你可以像使用其他组件一样导入和使用这个 <Link>

src/components/Link.astro
---
const { to } = Astro.props;
---
<a href={to}><slot /></a>

更新任何文件导入以准确引用相对文件路径。这可以通过 import aliases 来完成,或者通过写出相对路径的全称。

请注意,“.astro “和其他一些文件类型必须以其完整的文件扩展名导入。

src/pages/authors/Fred.astro
---
import Card from "../../components/Card.astro";
---
<Card />

转换 Next 的 Children Props 到 Astro

段落标题 转换 Next 的 Children Props 到 Astro

将任何 {children} 的实例转换为 Astro 的 <slot />。Astro 不需要接收 {children} 作为一个函数 prop,并会自动在 <slot /> 中渲染 child 内容。

src/components/MyComponent.astro
---
---
export default function MyComponent(props) {
return (
<div>
{props.children}
</div>
);
}
<div>
<slot />
</div>

可以使用命名插槽将有多个子组件的 React 组件迁移到 Astro 组件。

参考更多关于 Astro 中具体的 <slot /> 用法

转换 Next 的数据获取到 Astro

段落标题 转换 Next 的数据获取到 Astro

getStaticProps() 中任何实例转换为 Astro.glob()getCollection()/getEntryBySlug(),以便访问项目中其他文件的数据。为了获取远程数据,使用 fetch()

这些数据请求是在 Astro 组件的 frontmatter 中执行的,并使用 top-level await。

src/pages/index.astro
---
import { getCollection } from 'astro:content';
// 获取所有 `src/content/blog/` 的入口
const allBlogPosts = await getCollection('blog');
// 获取所有 `src/pages/posts/` 的入口
const allPosts = await Astro.glob('../pages/posts/*.md');
const response = await fetch('https://randomuser.me/api/');
const data = await response.json();
const randomUser = data.results[0];
---

查看更多关于Astro.glob() 导入本地文件使用 Collections API 查询获取远程数据

转换 Next 的样式到 Astro

段落标题 转换 Next 的样式到 Astro

你可能需要用 Astro 中其他可用的 CSS 选项来替换任何 CSS-in-JS 库(例如 styled-components)。

如果有必要,将任何内联样式对象(style={{ fontWeight: "bold" }})转换成内联 HTML 样式属性(style="font-weight:bold;")。或者,使用 Astro <style>标签实现 scoped CSS styles。

src/components/Card.astro
<div style={{backgroundColor: `#f4f4f4`, padding: `1em`}}>{message}</div>
<div style="background-color: #f4f4f4; padding: 1em;">{message}</div>

在安装了 Tailwind 集成之后,就支持 Tailwind。不需要对你现有的 Tailwind 代码进行任何修改

查看更多关于在 Astro 中设计样式 的信息。

转换 Next 的 图像插件到 Astro

段落标题 转换 Next 的 图像插件到 Astro

根据情况在你的 React 组件中将任何 Next 的 <Image /> 组件转换为 Astro 自己的图像集成组件或者转化成标准的 HTML <img> / JSX <img /> 标签

Astro 的 <Image /> 组件只能在 .astro.mdx 文件中工作。查看该组件可用的完整组件属性列表,并注意它与 Next 的属性有一些不同。

src/pages/index.astro
---
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 中使用图像的信息。

下面是一个 Next.js Pokédex 数据获取转换为 Astro 的例子。

pages/index.js 使用 REST PokéAPI 获取并显示前 151 个神奇宝贝的列表。

下面是如何在 src/pages/index.astro 中重新创建,将 getStaticProps() 替换为 fetch()

  1. 定义要返回的 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,
    },
    }
    }
  2. 创建 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>
  3. 添加任何需要的 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>

更多迁移指南

贡献

你有什么想法?

社区