Портфолио — визитная карточка разработчика или дизайнера. Оно должно загружаться мгновенно, выглядеть безупречно и демонстрировать техническую экспертизу. Astro — идеальный выбор: нулевой JS по умолчанию, PageSpeed 100, поддержка анимаций через Vue/React/Svelte острова.
Почему Astro для портфолио?
- PageSpeed 100 — потенциальный работодатель откроет Lighthouse и увидит идеал
- View Transitions API — плавные переходы между страницами без SPA
- Бесплатный хостинг — Cloudflare Pages, Vercel, Netlify
- Минимум зависимостей — никаких тяжелых фреймворков для статичных страниц
- TypeScript — демонстрирует серьёзность подхода
Структура портфолио
portfolio/
├── src/
│ ├── content/
│ │ └── projects/ # Описания проектов в MDX
│ │ ├── project-1.mdx
│ │ └── project-2.mdx
│ ├── components/
│ │ ├── Hero.astro # Главный экран с именем
│ │ ├── ProjectCard.astro # Карточка проекта
│ │ ├── SkillsGrid.astro # Сетка технологий
│ │ └── ContactForm.tsx # Форма (Preact-остров)
│ └── pages/
│ ├── index.astro # Главная (Hero + Проекты + Навыки)
│ ├── projects/
│ │ ├── index.astro # Все проекты
│ │ └── [slug].astro # Детальная страница проекта
│ └── contact.astro # Контакты Content Collection для проектов
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const projects = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/projects' }),
schema: ({ image }) =>
z.object({
title: z.string(),
description: z.string(),
tags: z.array(z.string()), // React, TypeScript, Astro...
cover: image().optional(),
url: z.string().url().optional(), // Ссылка на живой проект
github: z.string().url().optional(), // Ссылка на репозиторий
date: z.coerce.date(),
featured: z.boolean().default(false), // Показывать на главной
}),
});
export const collections = { projects }; Frontmatter проекта
---
title: 'E-commerce платформа для ювелирного бренда'
description: 'Разработал интернет-магазин с нуля: Astro + PocketBase + Stripe'
tags: ['Astro', 'TypeScript', 'PocketBase', 'Stripe', 'TailwindCSS']
url: 'https://jewelry-store.example.com'
github: 'https://github.com/username/jewelry-store'
date: 2026-03-15
featured: true
---
## О проекте
Клиент — небольшой ювелирный бренд, которому нужен был магазин... Hero-секция с анимацией
Используйте View Transitions для плавности без JavaScript-фреймворка:
---
// src/components/Hero.astro
---
<section class="hero">
<div class="hero__content">
<p class="hero__greeting">Привет, меня зовут</p>
<h1 class="hero__name" transition:name="name">Иван Иванов</h1>
<h2 class="hero__role">Frontend-разработчик</h2>
<p class="hero__bio">
Строю быстрые, доступные веб-сайты на Astro, React и TypeScript. Специализируюсь на
контентных проектах и e-commerce.
</p>
<div class="hero__cta">
<a href="/projects/" class="btn btn--primary">Мои работы</a>
<a href="/contact/" class="btn btn--outline">Написать</a>
</div>
</div>
</section> Галерея проектов
---
// src/pages/index.astro
import { getCollection } from 'astro:content';
import { Image } from 'astro:assets';
import ProjectCard from '../components/ProjectCard.astro';
const allProjects = await getCollection('projects');
const featuredProjects = allProjects
.filter((p) => p.data.featured)
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
---
<section>
<h2>Избранные проекты</h2>
<div class="projects-grid">
{
featuredProjects.map((project) => (
<ProjectCard
title={project.data.title}
description={project.data.description}
tags={project.data.tags}
url={project.data.url}
slug={project.id.replace('.mdx', '')}
cover={project.data.cover}
/>
))
}
</div>
<a href="/projects/">Все проекты →</a>
</section> Секция навыков
---
// src/components/SkillsGrid.astro
const skills = [
{ category: 'Frontend', items: ['Astro', 'React', 'TypeScript', 'TailwindCSS'] },
{ category: 'Backend', items: ['Node.js', 'PocketBase', 'PostgreSQL', 'Redis'] },
{ category: 'DevOps', items: ['Docker', 'Nginx', 'Cloudflare', 'GitHub Actions'] },
{ category: 'Инструменты', items: ['Figma', 'Git', 'Vite', 'Vitest'] },
];
---
<section>
<h2>Технологии</h2>
<div class="skills-grid">
{
skills.map((group) => (
<div class="skill-group">
<h3>{group.category}</h3>
<ul>
{group.items.map((skill) => (
<li>
<span class="badge">{skill}</span>
</li>
))}
</ul>
</div>
))
}
</div>
</section> View Transitions
Добавьте <ViewTransitions /> в BaseLayout. Переходы между страницами портфолио
станут плавными — как в SPA, но без React.
Метрики проектов
В описании каждого проекта добавьте конкретные числа: «Скорость загрузки выросла на 340%», «Конверсия +12%».
Dark Mode
Реализуйте через CSS prefers-color-scheme и маленький Preact-остров для ручного
переключения. Хранить выбор — в localStorage.
Форма контакта
// src/components/ContactForm.tsx (Preact-остров)
import { useState } from 'preact/hooks';
export default function ContactForm() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
async function handleSubmit(e: Event) {
e.preventDefault();
setStatus('loading');
const form = e.target as HTMLFormElement;
const data = Object.fromEntries(new FormData(form));
try {
await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});
setStatus('success');
} catch {
setStatus('error');
}
}
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Ваше имя" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Расскажите о проекте" rows={5} required />
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Отправка...' : 'Отправить'}
</button>
{status === 'success' && <p>Сообщение отправлено!</p>}
</form>
);
} В contact.astro:
---
import ContactForm from '../components/ContactForm.tsx';
---
<ContactForm client:load /> SEO для портфолио
Особое внимание — structured data Person:
---
const personSchema = {
'@context': 'https://schema.org',
'@type': 'Person',
name: 'Иван Иванов',
jobTitle: 'Frontend Developer',
url: 'https://ivanivanov.ru',
sameAs: [
'https://github.com/ivanivanov',
'https://linkedin.com/in/ivanivanov',
'https://t.me/ivanivanov',
],
knowsAbout: ['Astro', 'React', 'TypeScript', 'Web Development'],
};
---
<script type="application/ld+json" set:html={JSON.stringify(personSchema)} /> Деплой и домен
Бесплатные варианты:
- Cloudflare Pages —
yourname.pages.devили свой домен - GitHub Pages —
username.github.io - Vercel —
yourname.vercel.app
Для профессионального портфолио рекомендуем купить домен имяфамилия.ru (~150 руб/год). Инструкция по деплою — в статье Деплой и хостинг для Astro.
Итог
Портфолио на Astro — это заявление о вашей технической зрелости. PageSpeed 100, плавные View Transitions, строгая типизация проектов через Content Collections — всё это говорит о вас больше, чем любое резюме. Потратьте выходные — получите визитку, которая работает на вас круглосуточно.