Миграция Next.js Astro

Миграция с Next.js на Astro: Полное руководство

Пошаговое руководство по миграции вашего Next.js приложения на Astro с минимальным временем простоя, включая маршрутизацию, получение данных и стратегии развертывания.

Майк Чен
Майк Чен
12 мин чтения

Миграция с Next.js на Astro: Полное руководство

Миграция с Next.js на Astro может значительно улучшить производительность вашего сайта, сохраняя при этом привычный опыт разработки. Это всеобъемлющее руководство проведет вас через весь процесс миграции, от планирования до развертывания.

Зачем мигрировать на Astro?

Преимущества производительности

  • Ноль JavaScript по умолчанию: Отправляйте JavaScript только когда нужно
  • Более быстрое время сборки: Оптимизированный процесс сборки Astro
  • Лучшие Core Web Vitals: Улучшенные показатели LCP, FID и CLS
  • Меньшие размеры бандлов: Уменьшенный клиентский JavaScript

Опыт разработчика

  • Компонентные острова: Используйте React, Vue, Svelte вместе
  • Знакомый синтаксис: Похож на JSX с расширенными возможностями
  • Встроенные оптимизации: Оптимизация изображений, бандлинг CSS
  • Поддержка TypeScript: Первоклассная интеграция TypeScript

Оценка перед миграцией

Анализ вашего текущего Next.js приложения

Перед началом миграции проведите аудит существующего приложения:

# Анализ размера бандла
npx @next/bundle-analyzer

# Проверка зависимостей
npm list --depth=0

# Обзор структуры страниц
find pages -name "*.js" -o -name "*.tsx" | wc -l

Чек-лист совместимости

  • Статические страницы против динамических страниц
  • Использование API маршрутов
  • Требования клиентской маршрутизации
  • Интеграции третьих сторон
  • Кастомная конфигурация webpack
  • Использование middleware

Шаг 1: Настройка проекта

Инициализация проекта Astro

# Создание нового проекта Astro
npm create astro@latest my-astro-site

# Выбор шаблона
 Как вы хотите начать новый проект? Только основы
 Установить зависимости? Да
 Инициализировать новый git репозиторий? Да
 TypeScript? Да

Установка интеграции React

Поскольку вы мигрируете с Next.js, вероятно, захотите продолжить использовать React:

npx astro add react
npx astro add tailwind  # Если используете Tailwind CSS

Сравнение структуры проекта

Структура Next.js:
pages/
├── _app.js
├── _document.js
├── index.js
├── about.js
└── api/
    └── users.js

Структура Astro:
src/
├── layouts/
│   └── Layout.astro
├── pages/
│   ├── index.astro
│   └── about.astro
├── components/
└── content/

Шаг 2: Миграция макетов

Конвертация _app.js в Layout

Next.js _app.js:

import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return (
    <div>
      <header>Мой сайт</header>
      <Component {...pageProps} />
      <footer>© 2024</footer>
    </div>
  )
}

export default MyApp

Astro Layout.astro:

---
export interface Props {
  title: string;
}

const { title } = Astro.props;
---

<!DOCTYPE html>
<html lang="ru">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{title}</title>
  </head>
  <body>
    <header>Мой сайт</header>
    <main>
      <slot />
    </main>
    <footer>© 2024</footer>
  </body>
</html>

Шаг 3: Миграция страниц

Статические страницы

Страница Next.js:

// pages/about.js
import Head from 'next/head'

export default function About() {
  return (
    <>
      <Head>
        <title>О нас</title>
      </Head>
      <div>
        <h1>О нас</h1>
        <p>Добро пожаловать в нашу компанию!</p>
      </div>
    </>
  )
}

Страница Astro:

---
// src/pages/about.astro
import Layout from '../layouts/Layout.astro';
---

<Layout title="О нас">
  <div>
    <h1>О нас</h1>
    <p>Добро пожаловать в нашу компанию!</p>
  </div>
</Layout>

Динамические страницы с getStaticProps

Динамическая страница Next.js:

// pages/blog/[slug].js
export async function getStaticProps({ params }) {
  const post = await fetchPost(params.slug);
  return { props: { post } };
}

export async function getStaticPaths() {
  const posts = await fetchAllPosts();
  return {
    paths: posts.map(post => ({ params: { slug: post.slug } })),
    fallback: false
  };
}

export default function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

Динамическая страница Astro:

---
// src/pages/blog/[slug].astro
import Layout from '../../layouts/Layout.astro';

export async function getStaticPaths() {
  const posts = await fetchAllPosts();
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post }
  }));
}

const { post } = Astro.props;
---

<Layout title={post.title}>
  <article>
    <h1>{post.title}</h1>
    <p>{post.content}</p>
  </article>
</Layout>

Шаг 4: Миграция компонентов

Конвертация React компонентов

Большинство React компонентов можно использовать напрямую в Astro с интеграцией React:

Создание React компонента:

// src/components/Counter.jsx
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Счетчик: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Увеличить
      </button>
    </div>
  );
}

Использование в странице Astro:

---
// src/pages/interactive.astro
import Layout from '../layouts/Layout.astro';
import Counter from '../components/Counter.jsx';
---

<Layout title="Интерактивная страница">
  <h1>Интерактивные элементы</h1>
  <Counter client:load />
</Layout>

Клиентские директивы

Клиентские директивы Astro контролируют, когда компоненты гидратируются:

  • client:load - Гидратация немедленно
  • client:idle - Гидратация когда браузер простаивает
  • client:visible - Гидратация когда компонент виден
  • client:media - Гидратация на основе медиа-запроса

Шаг 5: Миграция получения данных

Статическое получение данных

Next.js getStaticProps:

export async function getStaticProps() {
  const data = await fetch('https://api.example.com/data');
  return { props: { data } };
}

Astro frontmatter:

---
const response = await fetch('https://api.example.com/data');
const data = await response.json();
---

<Layout title="Страница с данными">
  <div>{JSON.stringify(data)}</div>
</Layout>

Коллекции контента

Для блог-постов и похожего контента используйте коллекции контента Astro:

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    publishDate: z.date(),
    tags: z.array(z.string()),
  }),
});

export const collections = { blog };
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';

const allPosts = await getCollection('blog');
---

<Layout title="Блог">
  <h1>Посты блога</h1>
  {allPosts.map(post => (
    <article>
      <h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
      <p>{post.data.description}</p>
    </article>
  ))}
</Layout>

Шаг 6: Миграция API маршрутов

Простые API маршруты

API маршрут Next.js:

// pages/api/hello.js
export default function handler(req, res) {
  res.status(200).json({ message: 'Привет мир' });
}

API маршрут Astro:

// src/pages/api/hello.ts
export async function GET() {
  return new Response(JSON.stringify({ message: 'Привет мир' }), {
    status: 200,
    headers: {
      'Content-Type': 'application/json'
    }
  });
}

Сложные API маршруты

// src/pages/api/users/[id].ts
export async function GET({ params }) {
  const { id } = params;
  const user = await fetchUser(id);
  
  if (!user) {
    return new Response(null, { status: 404 });
  }
  
  return new Response(JSON.stringify(user), {
    headers: { 'Content-Type': 'application/json' }
  });
}

export async function POST({ request }) {
  const data = await request.json();
  const user = await createUser(data);
  
  return new Response(JSON.stringify(user), {
    status: 201,
    headers: { 'Content-Type': 'application/json' }
  });
}

Шаг 7: Миграция стилей

CSS модули

CSS модули Next.js:

/* styles/Home.module.css */
.container {
  padding: 2rem;
}
import styles from '../styles/Home.module.css';

export default function Home() {
  return <div className={styles.container}>Контент</div>;
}

Scoped стили Astro:

---
// src/pages/index.astro
---

<div class="container">Контент</div>

<style>
  .container {
    padding: 2rem;
  }
</style>

Глобальные стили

---
// src/layouts/Layout.astro
import '../styles/global.css';
---

<html>
  <!-- Контент макета -->
</html>

Шаг 8: Оптимизация изображений

Компонент изображений Next.js

Next.js:

import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Главное изображение"
  width={800}
  height={600}
  priority
/>

Astro:

---
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
---

<Image
  src={heroImage}
  alt="Главное изображение"
  width={800}
  height={600}
  loading="eager"
/>

Шаг 9: Миграция маршрутизации

Файловая маршрутизация

И Next.js, и Astro используют файловую маршрутизацию, но с некоторыми различиями:

Next.js:
pages/blog/[slug].js → /blog/:slug
pages/blog/[...slug].js → /blog/*

Astro:
src/pages/blog/[slug].astro → /blog/:slug
src/pages/blog/[...slug].astro → /blog/*

Динамические маршруты

---
// src/pages/products/[category]/[id].astro
export async function getStaticPaths() {
  return [
    { params: { category: 'electronics', id: '1' } },
    { params: { category: 'books', id: '2' } },
  ];
}

const { category, id } = Astro.params;
---

<Layout title={`Товар ${id} в ${category}`}>
  <h1>Товар {id}</h1>
  <p>Категория: {category}</p>
</Layout>

Шаг 10: Переменные окружения

Конфигурация

Next.js:

// next.config.js
module.exports = {
  env: {
    CUSTOM_KEY: process.env.CUSTOM_KEY,
  },
}

Astro:

// astro.config.mjs
export default defineConfig({
  // Конфигурация
});

Использование

---
// Доступ к переменным окружения
const apiKey = import.meta.env.PUBLIC_API_KEY;
const secretKey = import.meta.env.SECRET_KEY; // Только на сервере
---

Шаг 11: Тестирование миграции

Тестирование компонентов

// tests/components/Counter.test.jsx
import { render, fireEvent } from '@testing-library/react';
import Counter from '../src/components/Counter.jsx';

test('счетчик увеличивается', () => {
  const { getByText } = render(<Counter />);
  const button = getByText('Увеличить');
  fireEvent.click(button);
  expect(getByText('Счетчик: 1')).toBeInTheDocument();
});

Тестирование страниц

// tests/pages/about.test.js
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import About from '../src/pages/about.astro';

test('страница о нас рендерится', async () => {
  const container = await AstroContainer.create();
  const result = await container.renderToString(About);
  expect(result).toContain('О нас');
});

Шаг 12: Оптимизация производительности

Анализ бандла

# Анализ сборки Astro
npm run build
npx astro build --analyze

Чек-лист оптимизации

  • Удалить неиспользуемые зависимости
  • Оптимизировать изображения с компонентом Image Astro
  • Правильно использовать клиентские директивы
  • Реализовать правильные заголовки кеширования
  • Минимизировать клиентский JavaScript

Шаг 13: Развертывание

Развертывание на Vercel

// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'server',
  adapter: vercel(),
});

Развертывание на Netlify

// astro.config.mjs
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify/functions';

export default defineConfig({
  output: 'server',
  adapter: netlify(),
});

Чек-лист миграции

Перед миграцией

  • Аудит текущего Next.js приложения
  • Определение статических против динамических страниц
  • Список всех зависимостей
  • План стратегии миграции

Во время миграции

  • Настройка проекта Astro
  • Миграция макетов и компонентов
  • Конвертация страниц одна за другой
  • Обновление маршрутизации и навигации
  • Миграция API маршрутов
  • Тестирование функциональности

После миграции

  • Тестирование производительности
  • Проверка SEO
  • Кроссбраузерное тестирование
  • Развертывание на staging
  • Мониторинг Core Web Vitals
  • Обновление документации

Распространенные подводные камни и решения

1. Управление клиентским состоянием

Проблема: Глобальное состояние не работает между страницами Решение: Используйте постоянное состояние Astro или переходите на клиентскую маршрутизацию

2. Динамические импорты

Проблема: Динамические импорты ведут себя по-разному Решение: Используйте динамические импорты Astro или компонентные острова

3. CSS-in-JS библиотеки

Проблема: Styled-components не работают Решение: Используйте scoped стили Astro или CSS модули

Сравнение производительности

После миграции вы должны увидеть улучшения в:

  • Производительность Lighthouse: 90+ баллов
  • Размер бандла: Уменьшение на 50-80%
  • Time to Interactive: Значительное улучшение
  • Core Web Vitals: Лучшие показатели LCP, FID, CLS

Заключение

Миграция с Next.js на Astro может значительно улучшить производительность вашего сайта, сохраняя при этом продуктивность разработчика. Ключ в том, чтобы планировать тщательно, мигрировать постепенно и тщательно тестировать.

Помните:

  • Начинайте сначала со статических страниц
  • Используйте React компоненты там, где нужна интерактивность
  • Используйте клиентские директивы Astro для оптимальной производительности
  • Тестируйте каждый шаг миграции
  • Отслеживайте улучшения производительности

Усилия по миграции стоят того ради прироста производительности и улучшенного пользовательского опыта, которые предоставляет Astro.