Skip to content

Latest commit

 

History

History
581 lines (434 loc) · 23 KB

from-nextjs.mdx

File metadata and controls

581 lines (434 loc) · 23 KB
title description type stub framework i18nReady
从 Next.js 迁移
将现有的 Next.js 项目迁移到 Astro 的提示
migration
false
Next.js
true

import { Steps } from '@astrojs/starlight/components'; import AstroJSXTabs from '/components/tabs/AstroJSXTabs.astro'; import PackageManagerTabs from '/components/tabs/PackageManagerTabs.astro';

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

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 转换到 Astro 时,有一些常见的操作可以参考。

创建一个新的 Astro 项目

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

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

```shell # 启动 Astro CLI 安装向导 npm create astro@latest
# 通过官方示例来创建一个新项目
npm create astro@latest -- --template <example-name>
```
</Fragment>
<Fragment slot="pnpm">
```shell
# 启动 Astro CLI 安装向导
pnpm create astro@latest

# 通过官方示例来创建一个新项目
pnpm create astro@latest --template <example-name>
```
</Fragment>
<Fragment slot="yarn">
```shell
# 启动 Astro CLI 安装向导
yarn create astro@latest

# 通过官方示例来创建一个新项目
yarn create astro@latest --template <example-name>
```
</Fragment>

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

:::tip 访问 https://astro.new 了解官方启动模板的完整列表,以及在 StackBlitz、CodeSandbox 或 Gitpod 中打开一个新项目的链接。 :::

安装集成 (可选)

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

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

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

将源码放在 src 目录内

按照 Astro 的项目结构

1. **保持** Next 的 `public/` 文件夹不动。
Astro 使用 `public/` 目录来存放静态资产,就像 Next 一样。这个文件夹和它的内容都不需要改变。
  1. 复制或移动 Next 的其他文件和文件夹(如 pagesstyles 等)到 Astro 的 src/ 文件夹,当你在重构网站时,遵循 Astro 的项目结构

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

Astro 的配置文件

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

提示:将 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 文件逐步转换的例子

比较:JSX vs Astro

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

```jsx title="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;
```
```astro title="StarCount.astro" --- 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>
```

迁移布局文件

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

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

每个 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 目录

你的 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>
    );
  }
}
1. 开发一个新的只返回 JSX 的 Astro 布局文件。
  1. <html><head><slot> 和其他 HTML 标签替换 React 组件。。

    <html lang="en">
      <head>
          <link rel="icon" href="/favicon.ico" />
      </head>
      <body>
        <slot/>
      </body>
    </html>

迁移 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>
    </>
  );
}
1. 开发一个新的只返回 JSX 的 Astro 布局文件。
  1. 用一个单一的 Astro 布局文件替换这两个文件,其中包含一个页面外壳(<html><head><body> 标签)和一个 <slot/>,而不是 React 的 {children} props:

    <html lang="en">
      <head>
          <title>My Page</title>
      </head>
      <body>
        <slot/>
      </body>
    </html>

迁移 Pages 和 Posts

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

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

React 页面

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

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

Markdown 和 MDX 页面

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 <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

将任何 {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

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

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

---
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

你可能需要用 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 集成之后,就支持 Tailwind。不需要对你现有的 Tailwind 代码进行任何修改

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

转换 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 中使用图像的信息。

示例:数据请求

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

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

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

1. 定义要返回的 JSX。
```jsx title="pages/index.js" {6-18}
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,
        },
    }
}
```
  1. 创建 src/pages/index.astro

    使用 Next 函数的返回值。将任何 Next 或 React 语法转换成 Astro,包括改变任何 HTML 全局属性的大小写。

    注意:

    • .map 就已经可以了

    • className 变成 class

    • <Link> 变成 <a>

    • <> </> 在 Astro 模板设计中是不需要的。

    • key 是一个 React 属性而不是 li 标签的一个属性。

    ---
    ---
    <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>
  2. 添加任何需要的 imports, props, 和 JavaScript

    注意:

    • 不再需要 getStaticProps 函数。来自 API 的数据直接在代码栅栏中被获取。
    • 一个 <Layout> 组件被导入并包裹了页面模板。
    ---
    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>

社区资源