Skip to content

从 NuxtJS 迁移

以下是一些可帮助你入门的关键概念和迁移策略。使用我们的其余文档和 Discord 社区 继续前进!

¥Here are some key concepts and migration strategies to help you get started. Use the rest of our docs and our Discord community to keep going!

本指南指的是 Nuxt 2,而不是较新的 Nuxt 3。虽然某些概念相似,但 Nuxt 3 是该框架的较新版本,部分迁移可能需要不同的策略。

¥This guide is referring to Nuxt 2, not the newer Nuxt 3. While some of the concepts are similar, Nuxt 3 is a newer version of the framework and may require different strategies for parts of your migration.

Nuxt 和 Astro 之间的主要相似之处

Section titled Nuxt 和 Astro 之间的主要相似之处

¥Key Similarities between Nuxt and Astro

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

¥Nuxt and Astro share some similarities that will help you migrate your project:

Nuxt 和 Astro 之间的主要区别

Section titled Nuxt 和 Astro 之间的主要区别

¥Key Differences between Nuxt and Astro

当你在 Astro 中重建 Nuxt 站点时,你会注意到一些重要的差异:

¥When you rebuild your Nuxt site in Astro, you will notice some important differences:

  • Nuxt 是一个基于 Vue 的 SPA(单页应用)。Astro 网站是使用 .astro 组件构建的多页面应用,但也可以支持 React、Preact、Vue.js、Svelte、SolidJS、AlpineJS、Lit 和原始 HTML 模板。

  • 页面路由:Nuxt 使用 vue-router 进行 SPA 路由,使用 vue-meta 管理 <head>。在 Astro 中,你将创建单独的 HTML 页面路由并直接或在布局组件中控制页面 <head>

  • content-driven:Astro 旨在展示你的内容并允许你仅根据需要选择加入交互。现有的 Nuxt 应用可能是为高客户端交互性而构建的。Astro 具有处理内容的内置功能,例如页面生成,但可能需要高级的 Astro 技术来包含使用 .astro 组件复制更具挑战性的项目,例如仪表板。

¥Convert your NuxtJS Project

每个项目迁移看起来都会有所不同,但是从 Nuxt 转换到 Astro 时你将执行一些常见操作。

¥Each project migration will look different, but there are some common actions you will perform when converting from Nuxt to Astro.

创建一个新的 Astro 项目

Section titled 创建一个新的 Astro 项目

¥Create a new Astro project

使用包管理器的 create astro 命令来启动 Astro 的 CLI 向导或从 Astro 主题展示 中选择社区主题。

¥Use the create astro command for your package manager to launch Astro’s CLI wizard or choose a community theme from the Astro Theme Showcase.

你可以将 --template 参数传递给 create astro 命令,以使用我们的官方启动器之一启动新的 Astro 项目(例如 docsblogportfolio)。或者,你可以 从 GitHub 上任何现有的 Astro 存储库启动一个新项目

¥You can pass a --template argument to the create astro command to start a new Astro project with one of our official starters (e.g. docs, blog, portfolio). Or, you can start a new project from any existing Astro repository on GitHub.

Terminal window
# launch the Astro CLI Wizard
npm create astro@latest
# create a new project with an official example
npm create astro@latest -- --template <example-name>

然后,将现有的 Nuxt 项目文件复制到 src 之外的单独文件夹中的新 Astro 项目。

¥Then, copy your existing Nuxt project files over to your new Astro project in a separate folder outside of src.

¥Install integrations (optional)

你可能会发现安装一些 Astro 的可选集成 以便在将 Nuxt 项目转换为 Astro 时使用非常有用:

¥You may find it useful to install some of Astro’s optional integrations to use while converting your Nuxt project to Astro:

  • @astrojs/vue:在新的 Astro 站点中重用一些现有的 Vue UI 组件,或者继续使用 Vue 组件进行编写。

  • @astrojs/mdx:从你的 Nuxt 项目中引入现有的 MDX 文件,或者在你的新 Astro 站点中使用 MDX。

将你的源代码放在 src

Section titled 将你的源代码放在 src 中

¥Put your source code in src

  1. Move the contents of Nuxt’s static/ folder into public/.

    Astro uses the public/ directory for static assets, similar to Nuxt’s static/ folder.

  2. Copy or Move Nuxt’s other files and folders (e.g. pages, layouts etc.) into Astro’s src/ folder.

    Like Nuxt, Astro’s src/pages/ folder is a special folder used for file-based routing. All other folders are optional, and you can organize the contents of your src/ folder any way you like. Other common folders in Astro projects include src/layouts/, src/components, src/styles, src/scripts.

将 Vue SFC 页面转换为 .astro 文件

Section titled 将 Vue SFC 页面转换为 .astro 文件

¥Convert Vue SFC pages to .astro files

以下是将 Nuxt .vue 组件转换为 .astro 组件的一些技巧:

¥Here are some tips for converting a Nuxt .vue component into a .astro component:

  1. 使用现有 NuxtJS 组件功能的 <template> 作为 HTML 模板的基础。

  2. 更改任何 Nuxt 或 Vue 语法到 Astro 或 HTML Web 标准。例如,这包括 <NuxtLink>:class{{variable}}v-if

  3. <script> JavaScript 移至 “代码围栏” (---)。将组件的数据获取属性转换为服务器端 JavaScript - 参见 Nuxt 数据获取到 Astro

  4. 使用 Astro.props 访问之前传递给 Vue 组件的任何其他 props。

  5. 确定是否有任何导入组件也需要转换为 Astro。安装官方集成后,你可以 使用 Astro 文件中现有的 Vue 组件。但是,你可能希望将它们转换为 Astro,尤其是当它们不需要交互时!

参见 逐步转换的 Nuxt 应用示例

¥See an example from a Nuxt app converted step-by-step.

¥Compare: Vue vs Astro

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

¥Compare the following Nuxt component and a corresponding Astro component:

Page.vue
<template>
<div>
<p v-if="message === 'Not found'">
The repository you're looking up doesn't exist
</p>
<div v-else>
<Header/>
<p class="banner">Astro has {{stars}} 🧑‍🚀</p>
<Footer />
</div>
</div>
</template>
<script>
import Vue from 'vue'
export default Vue.extend({
name: 'IndexPage',
async asyncData() {
const res = await fetch('https://api.github.com/repos/withastro/astro')
const json = await res.json();
return {
message: json.message,
stars: json.stargazers_count || 0,
};
}
});
</script>
<style scoped>
.banner {
background-color: #f4f4f4;
padding: 1em 1.5em;
text-align: center;
margin-bottom: 1em;
}
<style>

¥Migrating Layout Files

你可能会发现从将 Nuxt 布局和模板转换为 Astro 布局组件 开始很有帮助。

¥You may find it helpful to start by converting your Nuxt layouts and templates into Astro layout components.

每个 Astro 页面都明确要求存在 <html><head><body> 标签。你的 Nuxt layout.vue 和模板将不包含这些。

¥Each Astro page explicitly requires <html>, <head>, and <body> tags to be present. Your Nuxt layout.vue and templates will not include these.

注意标准的 HTML 模板,并直接访问 <head>

¥Note the standard HTML templating, and direct access to <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" />
<title>Astro</title>
</head>
<body>
<!-- Wrap the slot element with your existing layout templating -->
<slot />
</body>
</html>

你可能还希望重用 你的 Nuxt 页面的 head 属性 中的代码以包含其他站点元数据。请注意,Astro 既不使用 vue-meta,也不使用组件的 head 属性,而是直接创建 <head>。即使在 <head> 内,你也可以导入和使用组件来分离和组织你的页面内容。

¥You may also wish to reuse code from your Nuxt’s page’s head property to include additional site metadata. Notice that Astro uses neither vue-meta nor a component’s head property but instead creates <head> directly. You may import and use components, even within <head>, to separate and organize your page content.

¥Migrating Pages and Posts

在 NuxtJS 中,你的 pages 存在于 /pages 中。在 Astro 中,你所有的页面内容都必须位于 src/pagessrc/content 中。

¥In NuxtJS, your pages live in /pages. In Astro, all your page’s content must live within src/pages or src/content.

¥Vue Pages

你现有的 Nuxt Vue (.vue) 页面需要是 从 Vue 文件转换为 .astro 页面。你无法在 Astro 中使用现有的 Vue 页面文件。

¥Your existing Nuxt Vue (.vue) pages will need to be converted from Vue files to .astro pages. You cannot use an existing Vue page file in Astro.

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

¥These .astro pages must be located within src/pages/ and will have page routes generated automatically based on their file path.

¥Dynamic File Path Naming

在 Nuxt 中,动态页面使用下划线来表示动态页面属性,然后将其传递给页面生成:

¥In Nuxt, your dynamic pages use an underscore to represent a dynamic page property that’s then passed to the page generation:

  • Directorypages/
    • Directorypokemon/
      • _name.vue
    • index.vue
  • nuxt.config.js

要转换为 Astro,请将此带下划线的动态路径属性(例如 _name.vue)更改为用一对方括号括起来(例如 [name].astro):

¥To convert to Astro, change this underscored dynamic path property (e.g. _name.vue) to be wrapped in a pair of square brackets (e.g. [name].astro):

  • Directorysrc/
    • Directorypages/
      • Directorypokemon/
        • [name].astro
      • index.astro
  • astro.config.mjs

¥Markdown and MDX pages

Astro 内置对 Markdown 的支持以及对 MDX 文件的可选集成。你可以重复使用任何现有的 Markdown 和 MDX 页面,但它们可能需要对其 frontmatter 进行一些调整,例如添加 Astro 的特殊 layout frontmatter 属性

¥Astro has built-in support for Markdown and an optional integration for MDX files. You can reuse any existing Markdown and MDX pages, but they may require some adjustments to their frontmatter, such as adding Astro’s special layout frontmatter property.

你将不再需要为每个 Markdown 生成的路由手动创建页面或使用像 @nuxt/content 这样的外部包。这些文件可以放置在 src/pages/ 内,以利用基于文件的自动路由。

¥You will no longer need to manually create pages for each Markdown-generated route or use an external package like @nuxt/content. These files can be placed within src/pages/ to take advantage of automatic file-based routing.

内容收集 的一部分、Markdown 和 MDX 文件将位于 src/content/ 内的文件夹中时,你将位于 动态生成这些页面

¥When part of a content collection, Markdown and MDX files will live in folders within src/content/ and you will generate those pages dynamically.

¥Migrating Tests

由于 Astro 输出原始 HTML,因此可以使用构建步骤的输出编写端到端测试。如果你能够匹配 Nuxt 站点的标记,之前编写的任何端到端测试都可以开箱即用。可以在 Astro 中导入和使用 Jest 和 Vue 测试库等测试库来测试你的 Vue 组件。

¥As Astro outputs raw HTML, it is possible to write end-to-end tests using the output of the build step. Any end-to-end tests written previously might work out-of-the-box, if you have been able to match the markup of your Nuxt site. Testing libraries such as Jest and Vue Testing Library can be imported and used in Astro to test your Vue components.

有关更多信息,请参阅 Astro 的 测试指南

¥See Astro’s testing guide for more.

参考:将 NuxtJS 语法转换为 Astro

Section titled 参考:将 NuxtJS 语法转换为 Astro

¥Reference: Convert NuxtJS Syntax to Astro

¥Nuxt Local Variables to Astro

要在 Astro 组件的 HTML 中使用局部变量,请将一组两个大括号更改为一组大括号:

¥To use local variables in an Astro component’s HTML, change the set of two curly braces to one set of curly braces:

src/components/Component.astro
---
const message = "Hello!"
---
<p>{{message}}</p>
<p>{message}</p>

¥Nuxt Property Passing to Astro

要绑定 Astro 组件中的属性或组件属性,请将此语法更改为以下内容:

¥To bind an attribute or component property in an Astro component, change this syntax to the following:

src/components/Component.astro
---
---
<p v-bind:aria-label="message">...</p>
<!-- Or -->
<p :aria-label="message">...</p>
<!-- Also support component props -->
<Header title="Page"/>
<p aria-label={message}>...</p>
<!-- Also support component props -->
<Header title={"Page"}/>

¥Nuxt Links to Astro

将任何 Nuxt <NuxtLink to=""> 组件转换为 HTML <a href=""> 标签。

¥Convert any Nuxt <NuxtLink to=""> components to HTML <a href=""> tags.

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

Astro 不使用任何特殊的链接组件,但欢迎你构建自定义链接组件。然后,你可以像导入任何其他组件一样导入并使用该 <Link>

¥Astro does not use any special component for links, although you are welcome to build custom link components. You can then import and use this <Link> just as you would any other component.

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

¥Nuxt Imports to Astro

如有必要,更新任何 file imports 以准确引用相对文件路径。这可以使用 import aliases 或通过完整写出相对路径来完成。

¥If necessary, update any file imports to reference relative file paths exactly. This can be done using import aliases, or by writing out a relative path in full.

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

¥Note that .astro and several other file types must be imported with their full file extension.

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

Nuxt 动态页面生成到 Astro

Section titled Nuxt 动态页面生成到 Astro

¥Nuxt Dynamic Page Generation to Astro

在 Nuxt 中,要生成动态页面,你必须:

¥In Nuxt, to generate a dynamic page you either must:

在 Astro 中,你同样有两种选择:

¥In Astro, you similarly have two choices:

将 Nuxt 中的 generate 函数转换为 Astro 中的 getStaticPaths 函数。

Section titled 将 Nuxt 中的 generate 函数转换为 Astro 中的 getStaticPaths 函数。

¥Convert a generate function in Nuxt to a getStaticPaths function in Astro.

要生成多个页面,请将在 nuxt.config.js 中创建路由的函数替换为直接在动态路由页面本身内的 getStaticPaths()

¥To generate multiple pages, replace the function to create routes in your nuxt.config.js with getStaticPaths() directly inside a dynamic routing page itself:

nuxt.config.js
{
// ...
generate: {
async routes() {
// Axios is required here unless you're using Node 18
const res = await axios.get("https://pokeapi.co/api/v2/pokemon?limit=151")
const pokemons = res.data.results;
return pokemons.map(pokemon => {
return '/pokemon/' + pokemon.name
})
}
}
}
src/pages/pokemon/[name].astro
---
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 },
}))
}
// ...
---
<!-- Your template here -->

¥Nuxt Data Fetching to Astro

Nuxt 有两种获取服务器端数据的方法:

¥Nuxt has two methods of fetching server-side data:

在 Astro 中,获取页面代码围栏内的数据。

¥In Astro, fetch data inside of your page’s code fence.

迁移以下内容:

¥Migrate the following:

pages/index.vue
{
// ...
async asyncData() {
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
const resJson = await res.json();
const pokemons = resJson.results;
return {
pokemons,
}
},
}

对于没有封装函数的代码围栏:

¥To a code fence without a wrapper function:

src/pages/index.astro
---
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
const resJson = await res.json();
const pokemons = resJson.results;
---
<!-- Your template here -->

¥Nuxt Styling to Astro

Nuxt 利用 Vue 的组件样式来生成页面的样式。

¥Nuxt utilizes Vue’s component styling to generate a page’s style.

pages/index.vue
<template>
<!-- Your template here -->
</template>
<script>
// Your server logic here
</script>
<style scoped>
.class {
color: red;
}
</style>

同样,在 Astro 中,你可以在页面模板中放入 <style> 元素,以为组件提供范围样式。

¥Similarly, in Astro you can drop in a <style> element in your page’s template to provide scoped styles to the component.

src/pages/index.vue
---
// Your server logic here
---
<style>
.class {
color: red;
}
</style>

¥Global Styling

在 Astro 中,<style> 标签默认为 scoped。要使 <style> 标记成为全局标记,请使用 is:global 属性对其进行标记:

¥<style> tags are scoped by default in Astro. To make a <style> tag global, mark it with the is:global attribute:

src/pages/index.vue
<style is:global>
p {
color: red;
}
</style>

¥Pre-processor support

Astro 支持最流行的 CSS 预处理器 通过将它们安装为开发依赖来实现。例如,要使用 SCSS:

¥Astro supports the most popular CSS preprocessors by installing them as a dev dependency. For example, to use SCSS:

Terminal window
npm install -D sass

完成此操作后,你就可以使用 .scss.sass 样式,而无需从 Vue 组件进行修改。

¥After doing so, you’re then able to use .scss or .sass styled without modification from your Vue components.

src/layouts/Layout.astro
<p>Hello, world</p>
<style lang="scss">
p {
color: black;
&:hover {
color: red;
}
}
</style>

查看更多有关 Astro 样式 的信息。

¥See more about Styling in Astro.

¥Nuxt Image Plugin to Astro

.astro.mdx 文件中的任何 Nuxt <nuxt-img/><nuxt-picture/> 组件 转换为 Astro 自带的图片组件,或者根据你的 Vue 组件中的情况转换为 标准 HTML <img><picture> 标记。

¥Convert any Nuxt <nuxt-img/> or <nuxt-picture/> components to Astro’s own image component in .astro or .mdx files, or to a standard HTML <img> or <picture> tag as appropriate in your Vue components.

Astro 的 <Image /> 组件仅适用于 .astro.mdx 文件。请参阅 其组件属性的完整列表,并注意其中几个属性与 Nuxt 的属性不同。

¥Astro’s <Image /> component works in .astro and .mdx files only. See a full list of its component attributes and note that several will differ from Nuxt’s attributes.

src/pages/index.astro
---
import { Image } from 'astro:assets';
import rocket from '../assets/rocket.png';
---
<Image src={rocket} alt="A rocketship in space." />
<img src={rocket.src} alt="A rocketship in space.">

在 Astro 应用内的 Vue (.vue) 组件中,使用标准 JSX 图片语法 (<img />)。Astro 不会优化这些映像,但你可以安装和使用 NPM 包以获得更大的灵活性。

¥In Vue (.vue) components within your Astro app, use standard JSX image syntax (<img />). Astro will not optimize these images, but you can install and use NPM packages for more flexibility.

你可以在图片指南中了解有关 在 Astro 中使用图片 的更多信息。

¥You can learn more about using images in Astro in the Images Guide.

指导示例:查看步骤!

Section titled 指导示例:查看步骤!

¥Guided example: See the steps!

以下是 Nuxt Pokédex 数据获取转换为 Astro 的示例。

¥Here is an example of Nuxt Pokédex data fetch converted to Astro.

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

¥pages/index.vue fetches and displays a list of the first 151 Pokémon using the REST PokéAPI.

以下是如何在 src/pages/index.astro 中重新创建它,用 fetch() 替换 asyncData()

¥Here’s how to recreate that in src/pages/index.astro, replacing asyncData() with fetch().

  1. Identify the <template> and <style> in the Vue SFC.

    pages/index.vue
    <template>
    <ul class="plain-list pokeList">
    <li v-for="pokemon of pokemons" class="pokemonListItem" :key="pokemon.name">
    <NuxtLink class="pokemonContainer" :to="`/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>
    </NuxtLink>
    </li>
    </ul>
    </template>
    <script>
    import Vue from 'vue'
    export default Vue.extend({
    name: 'IndexPage',
    layout: 'default',
    async asyncData() {
    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 {
    pokemons,
    }
    },
    head() {
    return {
    title: "Pokedex: Generation 1"
    }
    }
    });
    </script>
    <style scoped>
    .pokeList {
    display: grid;
    grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) );
    gap: 1rem;
    }
    /* ... */
    </style>
  2. Create src/pages/index.astro

    Use the <template> and <style> tags of the Nuxt SFC. Convert any Nuxt or Vue syntax to Astro.

    Note that:

    • <template> is removed

    • <style> has its scoped attribute removed

    • v-for becomes .map.

    • :attr="val" becomes attr={val}

    • <NuxtLink> becomes <a>.

    • The <> </> fragment is not required in Astro templating.

    src/pages/index.astro
    ---
    ---
    <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>
    <style>
    .pokeList {
    display: grid;
    grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) );
    gap: 1rem;
    }
    /* ... */
    </style>
  3. Add any needed imports, props and JavaScript

    Note that:

    • The asyncData 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.
      • Our head() Nuxt method is passed to the <Layout> component, which is passed to the <title> element as a property.
    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 title="Pokedex: Generation 1">
    <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>
    <style>
    .pokeList {
    display: grid;
    grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) );
    gap: 1rem;
    }
    /* ... */
    </style>

¥Community Resources

More migration guides

Astro 中文网 - 粤ICP备13048890号