컨텐츠로 건너뛰기

NuxtJS에서 마이그레이션

다음은 시작하는 데 도움이 되는 몇 가지 주요 개념과 마이그레이션 전략입니다. 계속 진행하려면 나머지 문서와 Discord 커뮤니티를 활용하세요!

이 가이드는 최신 Nuxt 3이 아닌 Nuxt 2를 언급하고 있습니다. 일부 개념은 유사하지만 Nuxt 3은 프레임워크의 최신 버전이므로 마이그레이션 부분에 대해 다른 전략이 필요할 수 있습니다.

Nuxt와 Astro의 주요 유사점

섹션 제목: Nuxt와 Astro의 주요 유사점

Nuxt와 Astro는 프로젝트 마이그레이션에 도움이 되는 몇 가지 유사점을 공유합니다.

Nuxt와 Astro의 주요 차이점

섹션 제목: Nuxt와 Astro의 주요 차이점

Astro에서 Nuxt 사이트를 재빌드하면 몇 가지 중요한 차이점을 발견할 수 있습니다:

  • Nuxt는 Vue 기반 SPA (단일 페이지 애플리케이션)입니다. Astro 사이트는 .astro 컴포넌트를 사용하여 빌드된 다중 페이지 앱이지만 React, Preact, Vue.js, Svelte, SolidJS, AlpineJS 및 원시 HTML 템플릿도 지원할 수 있습니다.

  • 페이지 라우팅: Nuxt는 SPA 라우팅에 vue-router를 사용하고 <head> 관리에 vue-meta를 사용합니다. Astro에서는 별도의 HTML 페이지 경로를 생성하고 페이지 <head>를 직접 또는 레이아웃 컴포넌트에서 제어합니다.

  • 콘텐츠 중심: Astro는 콘텐츠를 선보이고 필요할 때만 상호 작용을 선택할 수 있도록 설계되었습니다. 기존 Nuxt 앱은 높은 클라이언트 측 상호작용을 위해 구축될 수 있습니다. Astro에는 페이지 생성과 같은 콘텐츠 작업을 위한 기능이 내장되어 있지만 대시보드와 같은 .astro 컴포넌트를 사용하여 복제하기가 더 어려운 항목을 포함하려면 고급 Astro 기술이 필요할 수 있습니다.

각 프로젝트 마이그레이션은 다르게 보이지만 Nuxt에서 Astro로 변환할 때 수행하게 되는 몇 가지 일반적인 작업이 있습니다.

새 Astro 프로젝트 만들기

섹션 제목: 새 Astro 프로젝트 만들기

패키지 관리자의 create astro 명령을 사용하여 Astro의 CLI 마법사를 시작하거나 Astro 테마 쇼케이스에서 커뮤니티 테마를 선택하세요.

create astro 명령에 --template 인수를 전달하여 공식 스타터 (예: docs, blog, portfolio) 중 하나를 사용하여 새 Astro 프로젝트를 시작할 수 있습니다. 또는 GitHub의 기존 Astro 저장소에서 새 프로젝트를 시작할 수 있습니다.

터미널 창
# Astro CLI 마법사 실행
npm create astro@latest
# 공식 예시를 사용하여 새 프로젝트 만들기
npm create astro@latest -- --template <example-name>

그런 다음 기존 Nuxt 프로젝트 파일을 src 외부의 별도 폴더에 있는 새 Astro 프로젝트로 복사하세요.

Nuxt 프로젝트를 Astro로 변환하는 동안 사용할 Astro의 선택적 통합 중 일부를 설치하는 것이 유용할 수 있습니다.

  • @astrojs/vue: 새 Astro 사이트에서 일부 기존 Vue UI 컴포넌트를 재사용하거나 Vue 컴포넌트로 계속 작성하기 위해 필요합니다.

  • @astrojs/mdx: Nuxt 프로젝트에서 기존 MDX 파일을 가져오거나 새 Astro 사이트에서 MDX를 사용하기 위해 필요합니다.

  1. Nuxt의 static/ 폴더 내용을 public/으로 이동하세요.

    Astro는 Nuxt의 static/ 폴더와 유사하게 정적 자산에 public/ 디렉터리를 사용합니다.

  2. Nuxt의 다른 파일 및 폴더 (예: pages, layouts 등)를 Astro의 src/ 폴더로 복사하거나 이동하세요.

    Nuxt와 마찬가지로 Astro의 src/pages/ 폴더는 파일 기반 라우팅에 사용되는 특수 폴더입니다. 다른 모든 폴더는 선택 사항이며 src/ 폴더의 내용을 원하는 대로 구성할 수 있습니다. Astro 프로젝트의 다른 일반적인 폴더에는 src/layouts/, src/components, src/styles, src/scripts가 있습니다.

Vue SFC 페이지를 .astro 파일로 변환

섹션 제목: Vue SFC 페이지를 .astro 파일로 변환

다음은 Nuxt .vue 컴포넌트를 .astro 컴포넌트로 변환하기 위한 몇 가지 팁입니다.

  1. 기존 NuxtJS 컴포넌트 함수의 <template>을 HTML 템플릿의 기초로 사용합니다.

  2. Nuxt 또는 Vue 구문을 Astro로 또는 HTML 웹 표준으로 변경하세요. 예를 들어 여기에는 <NuxtLink>, :class, {{variable}}, v-if가 포함됩니다.

  3. <script> JavaScript를 “코드 펜스” (---)로 이동합니다. 컴포넌트의 data-fetching 속성을 서버 측 JavaScript로 변환하세요. Astro로 Nuxt 데이터 가져오기를 참조하세요.

  4. Astro.props를 사용하여 이전에 Vue 컴포넌트에 전달된 추가 props에 액세스합니다.

  5. 가져온 컴포넌트도 Astro로 변환해야 하는지 여부를 결정합니다. 공식 통합이 설치되면 Astro 파일의 기존 Vue 컴포넌트를 사용할 수 있습니다. 그러나 특히 대화형이 필요하지 않은 경우 Astro로 변환하고 싶을 수도 있습니다!

단계별로 Nuxt 앱을 변환하는 예시를 참조하세요.

다음 Nuxt 컴포넌트와 해당 Astro 컴포넌트를 비교해 보세요.

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>

Layout 파일 마이그레이션

섹션 제목: Layout 파일 마이그레이션

Nuxt 레이아웃과 템플릿을 Astro 레이아웃 컴포넌트로 변환하여 시작하는 것이 도움이 될 수 있습니다.

각 Astro 페이지에는 <html>, <head>, <body> 태그가 명시적으로 필요합니다. Nuxt layout.vue 및 템플릿에는 이러한 항목이 포함되지 않습니다.

표준 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" />
<title>Astro</title>
</head>
<body>
<!-- 기존 레이아웃 템플릿으로 슬롯 요소를 래핑합니다. -->
<slot />
</body>
</html>

추가 사이트 메타데이터를 포함하기 위해 Nuxt 페이지의 head 속성의 코드를 재사용할 수도 있습니다. Astro는 vue-meta나 컴포넌트의 head 속성을 사용하지 않고 대신 <head>를 직접 생성합니다. <head> 내에서도 컴포넌트를 가져와 사용하여 페이지 콘텐츠를 분리하고 구성할 수 있습니다.

Pages 및 Posts 마이그레이션

섹션 제목: Pages 및 Posts 마이그레이션

Astro에서는 콘텐츠 컬렉션을 사용하지 않는 한 모든 페이지 콘텐츠가 src/에 있어야 합니다.

기존 Nuxt Vue (.vue) 페이지를 Vue 파일에서 .astro 페이지로 변환해야 합니다. Astro에서는 기존 Vue 페이지 파일을 사용할 수 없습니다.

이러한 .astro 페이지src/pages/ 내에 있어야 하며 파일 경로에 따라 페이지 경로가 자동으로 생성됩니다.

동적 파일 경로 이름 지정
섹션 제목: 동적 파일 경로 이름 지정

Nuxt에서 동적 페이지는 밑줄을 사용하여 페이지 생성에 전달되는 동적 페이지 속성을 나타냅니다.

  • 디렉터리pages/
    • 디렉터리pokemon/
      • _name.vue
    • index.vue
  • nuxt.config.js

Astro로 변환하려면 밑줄이 그어진 동적 경로 속성 (예: _name.vue)을 대괄호 쌍 (예: [name].astro)으로 묶도록 변경하세요.

  • 디렉터리src/
    • 디렉터리pages/
      • 디렉터리pokemon/
        • [name].astro
      • index.astro
  • astro.config.mjs

Astro에는 Markdown에 대한 기본 지원과 MDX 파일에 대한 선택적 통합이 있습니다. 기존 Markdown 및 MDX 페이지를 재사용할 수 있지만 Astro의 특수 layout frontmatter 속성을 추가하는 등 프런트매터에 일부 조정이 필요할 수 있습니다.

더 이상 Markdown으로 생성된 각 경로에 대한 페이지를 수동으로 생성하거나 @nuxt/content와 같은 외부 패키지를 사용할 필요가 없습니다. 이러한 파일은 src/pages/에 배치하여 자동 파일 기반 라우팅을 활용할 수 있습니다.

콘텐츠 컬렉션의 일부인 경우, 콘텐츠 항목으로부터 페이지를 동적으로 생성합니다.

Astro는 원시 HTML을 출력하므로 빌드 단계의 출력을 사용하여 end-to-end 테스트를 작성하는 것이 가능합니다. 이전에 작성된 모든 end-to-end 테스트는 Nuxt 사이트의 마크업과 일치할 수 있는 경우 즉시 작동할 수 있습니다. Jest 및 Vue Testing Library와 같은 테스트 라이브러리를 Astro에서 가져와 Vue 컴포넌트를 테스트하는 데 사용할 수 있습니다.

자세한 내용은 Astro의 테스트 가이드를 참조하세요.

참조: NuxtJS 구문을 Astro로 변환

섹션 제목: 참조: NuxtJS 구문을 Astro로 변환

Astro 컴포넌트의 HTML에서 지역 변수를 사용하려면 두 개의 중괄호 세트를 하나의 중괄호 세트로 변경하세요.

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

Astro 컴포넌트의 속성 또는 컴포넌트 속성을 바인딩하려면 이 구문을 다음과 같이 변경하세요.

src/components/Component.astro
---
---
<p v-bind:aria-label="message">...</p>
<!-- 또는 -->
<p :aria-label="message">...</p>
<!-- 컴포넌트 props도 지원 -->
<Header title="Page"/>
<p aria-label={message}>...</p>
<!-- 컴포넌트 props도 지원 -->
<Header title={"Page"}/>

Nuxt <NuxtLink to=""> 컴포넌트를 HTML <a href=""> 태그로 변환합니다.

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

Astro는 링크에 특별한 컴포넌트를 사용하지 않지만 사용자 정의 링크 컴포넌트를 만드는 것은 환영합니다. 그런 다음 다른 컴포넌트와 마찬가지로 이 <Link>를 가져와 사용할 수 있습니다.

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

필요한 경우 상대 파일 경로를 정확하게 참조하도록 파일 가져오기를 업데이트하세요. 이는 별칭 가져오기를 사용하거나 상대 경로 전체를 작성하여 수행할 수 있습니다.

.astro 및 기타 여러 파일 형식은 전체 파일 확장자를 사용하여 가져와야 합니다.

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

Nuxt 동적 페이지 생성 변환

섹션 제목: Nuxt 동적 페이지 생성 변환

Nuxt에서 동적 페이지를 생성하려면 다음 중 하나를 수행해야 합니다.

Astro에서도 마찬가지로 두 가지 선택이 있습니다.

Nuxt의 generate 함수를 Astro의 getStaticPaths 함수로 변환

섹션 제목: Nuxt의 generate 함수를 Astro의 getStaticPaths 함수로 변환

여러 페이지를 생성하려면 nuxt.config.js 파일에서 경로를 생성하는 함수를 동적 라우팅 페이지 내에서 직접 getStaticPaths()로 바꾸세요.

nuxt.config.js
{
// ...
generate: {
async routes() {
// Node 18을 사용하지 않는 한 여기에는 Axios가 필요합니다.
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 },
}))
}
// ...
---
<!-- 템플릿 작성 -->

Nuxt에는 서버 측 데이터를 가져오는 두 가지 방법이 있습니다.

Astro에서는 페이지의 코드 펜스 내부에서 데이터를 가져옵니다.

다음을 마이그레이션합니다.

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

래퍼 함수가 없는 코드 펜스의 경우:

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;
---
<!-- 템플릿 작성 -->

Nuxt는 Vue의 컴포넌트 스타일을 활용하여 페이지 스타일을 생성합니다.

pages/index.vue
<template>
<!-- 템플릿 작성 -->
</template>
<script>
// 서버 로직 작성
</script>
<style scoped>
.class {
color: red;
}
</style>

마찬가지로 Astro에서는 페이지 템플릿에 <style> 요소를 추가하여 컴포넌트에 범위가 지정된 스타일을 제공할 수 있습니다.

src/pages/index.vue
---
// 서버 로직 작성
---
<style>
.class {
color: red;
}
</style>

<style> 태그는 Astro에서 기본적으로 scoped입니다. <style> 태그를 전역으로 만들려면 is:global 속성으로 표시하세요.

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

Astro는 가장 널리 사용되는 CSS 전처리기를 지원합니다. 이를 개발 종속 항목으로 설치합니다. 예를 들어 SCSS를 사용하려면 다음과 같이 하세요.

터미널 창
npm install -D sass

그런 다음 Vue 컴포넌트를 수정하지 않고 스타일이 지정된 .scss 또는 .sass를 사용할 수 있습니다.

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

Astro에서의 스타일링에 대해 자세히 알아보세요.

Nuxt 이미지 플러그인 변환

섹션 제목: Nuxt 이미지 플러그인 변환

Nuxt <nuxt-img/> 또는 <nuxt-picture/> 컴포넌트.astro 또는 .mdx 파일의 Astro 자체 이미지 컴포넌트로 변환하거나 Vue 컴포넌트에서 적절하게 표준 HTML <img> 또는 <picture> 태그로 변환하세요.

Astro의 <Image /> 컴포넌트는 .astro.mdx 파일에서만 작동합니다. 컴포넌트 속성의 전체 목록을 확인하고 몇 가지 속성이 Nuxt의 속성과 다르다는 점에 유의하세요.

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 패키지를 설치하고 사용할 수 있습니다.

이미지 가이드에서 Astro에서 이미지 사용에 대해 자세히 알아볼 수 있습니다.

안내 예시: 단계를 확인하세요!

섹션 제목: 안내 예시: 단계를 확인하세요!

다음은 Astro로 변환된 Nuxt Pokédex 데이터 페칭의 예시입니다.

pages/index.vueREST PokéAPI를 사용하여 처음 151마리의 포켓몬 목록을 가져와 표시합니다.

src/pages/index.astro에서 이를 다시 생성하고 asyncData()fetch()로 바꾸는 방법은 다음과 같습니다.

  1. Vue SFC에서 <template><style>을 식별합니다.

    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. src/pages/index.astro를 생성합니다.

    Nuxt SFC의 <template><style> 태그를 사용하세요. Nuxt 또는 Vue 구문을 Astro로 변환하세요.

    참고 사항:

    • <template>이 제거되었습니다.

    • <style>scoped 속성이 제거되었습니다.

    • v-for.map이 됩니다.

    • :attr="val"attr={val}이 됩니다.

    • <NuxtLink><a>가 됩니다.

    • Astro 템플릿에는 <> </> 프래그먼트가 필요하지 않습니다.

    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. 필요한 imports, props, JavaScript를 추가하세요.

    참고 사항:

    • asyncData 함수는 더 이상 필요하지 않습니다. API의 데이터는 코드 펜스에서 직접 가져옵니다.
    • <Layout> 컴포넌트를 가져와 페이지 템플릿을 래핑합니다.
      • head() Nuxt 메소드는 <Layout> 컴포넌트에 전달되며, 이는 <title> 요소에 속성으로 전달됩니다.
    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>

더 많은 전환 안내서

기여하기

여러분의 생각을 들려주세요!

GitHub Issue 생성

우리에게 가장 빨리 문제를 알려줄 수 있어요.

커뮤니티