Migracja do Next.js: jak bezboleśnie przyspieszyć stronę www
Kompletny przewodnik migracji strony do Next.js. Krok po kroku, z zachowaniem SEO i bez przestojów. Case study z 60% poprawą wydajności.
Migracja do Next.js może przynieść 60% poprawę wydajności i znacznie lepsze SEO. W tym przewodniku pokażę, jak przeprowadzić bezbolesną migrację bez utraty pozycji w Google i przestojów.
Dlaczego migrować do Next.js?
Korzyści z migracji - realne liczby:
Szybkość ładowania poprawia się z 3.2s do 1.2s (62% poprawa), Lighthouse Score wzrasta z 67 do 94 punktów (+27 punktów), Bounce Rate spada z 45% do 28% (38% spadek), konwersja rośnie o 23% po migracji, a koszty hostingu maleją o 40% dzięki SSG.
Faza 1: Audyt i planowanie migracji
1. Analiza obecnej strony
# Audyt wydajności npx lighthouse https://twoja-strona.pl --output html --output-path ./audit-before.html # Analiza bundle size npm install -g webpack-bundle-analyzer webpack-bundle-analyzer dist/static/js/*.js # SEO crawl screaming-frog-seo-spider https://twoja-strona.pl
2. Inwentaryzacja zasobów
// audit-script.js - skrypt do analizy strony const puppeteer = require("puppeteer"); const fs = require("fs"); const auditWebsite = async url => { const browser = await puppeteer.launch(); const page = await browser.newPage(); // Analiza performance await page.goto(url, { waitUntil: "networkidle2" }); const metrics = await page.evaluate(() => { const navigation = performance.getEntriesByType("navigation")[0]; return { domContentLoaded: navigation.domContentLoadedEventEnd - navigation.fetchStart, loadComplete: navigation.loadEventEnd - navigation.fetchStart, firstPaint: performance.getEntriesByName("first-paint")[0]?.startTime, firstContentfulPaint: performance.getEntriesByName("first-contentful-paint")[0]?.startTime, }; }); // Analiza SEO const seoData = await page.evaluate(() => { return { title: document.title, metaDescription: document.querySelector('meta[name="description"]')?.content, h1Count: document.querySelectorAll("h1").length, imageCount: document.querySelectorAll("img").length, linksCount: document.querySelectorAll("a").length, }; }); // Zapisz raport const report = { url, metrics, seoData, timestamp: new Date() }; fs.writeFileSync("./audit-report.json", JSON.stringify(report, null, 2)); await browser.close(); return report; }; // Uruchom audyt auditWebsite("https://twoja-strona.pl");
3. Plan migracji - timeline
Timeline migracji obejmuje dziesięć tygodni pracy. Pierwsze dwa tygodnie to audyt i setup projektu (20 godzin), tygodnie 3-4 to migracja komponentów core (40 godzin), tygodnie 5-6 obejmują routing i API integration (30 godzin). Tygodnie 7-8 skupiają się na SEO i optymalizacjach (25 godzin), dziewiąty tydzień to testing i deployment (15 godzin), a dziesiąty to go-live i monitoring (10 godzin).
Faza 2: Setup projektu Next.js
1. Inicjalizacja projektu
# Utwórz nowy projekt Next.js npx create-next-app@latest nazwa-projektu --typescript --tailwind --eslint --app cd nazwa-projektu # Zainstaluj dodatkowe dependencje npm install @next/mdx @mdx-js/loader @mdx-js/react npm install next-sitemap next-seo npm install sharp # dla optymalizacji obrazów
2. Konfiguracja next.config.js
/** @type {import('next').NextConfig} */ const nextConfig = { // Experimental features experimental: { optimizePackageImports: ["lucide-react", "date-fns"], }, // Redirects z starej strony async redirects() { return [ { source: "/old-page", destination: "/new-page", permanent: true, }, { source: "/blog/:slug*", destination: "/articles/:slug*", permanent: true, }, ]; }, // Rewrites dla API async rewrites() { return [ { source: "/api/legacy/:path*", destination: "https://old-api.domain.com/:path*", }, ]; }, // Headers dla SEO i security async headers() { return [ { source: "/:path*", headers: [ { key: "X-DNS-Prefetch-Control", value: "on", }, { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload", }, { key: "X-Frame-Options", value: "DENY", }, ], }, ]; }, // Optymalizacja obrazów images: { formats: ["image/avif", "image/webp"], deviceSizes: [640, 750, 828, 1080, 1200, 1920], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], domains: ["old-domain.com", "cdn.old-domain.com"], }, }; module.exports = nextConfig;
3. Struktura folderów
src/
├── app/
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.tsx
│ ├── loading.tsx
│ ├── not-found.tsx
│ ├── sitemap.ts
│ └── robots.ts
├── components/
│ ├── ui/
│ ├── layout/
│ └── common/
├── lib/
│ ├── utils.ts
│ ├── validations.ts
│ └── api.ts
├── hooks/
├── types/
└── data/
Faza 3: Migracja komponentów
1. Layout i nawigacja
// components/layout/MainLayout.tsx import { Header } from './Header' import { Footer } from './Footer' import { Sidebar } from './Sidebar' interface MainLayoutProps { children: React.ReactNode showSidebar?: boolean } export function MainLayout({ children, showSidebar = false }: MainLayoutProps) { return ( <div className="min-h-screen flex flex-col"> <Header /> <main className="flex-1 flex"> {showSidebar && ( <aside className="w-64 bg-gray-50"> <Sidebar /> </aside> )} <div className="flex-1"> {children} </div> </main> <Footer /> </div> ) }
2. Migracja formularzy
// components/forms/ContactForm.tsx 'use client' import { useState } from 'react' import { useRouter } from 'next/navigation' export function ContactForm() { const [formData, setFormData] = useState({ name: '', email: '', message: '' }) const [isSubmitting, setIsSubmitting] = useState(false) const router = useRouter() const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setIsSubmitting(true) try { const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), }) if (response.ok) { router.push('/thank-you') } else { throw new Error('Błąd wysyłania formularza') } } catch (error) { console.error('Error:', error) } finally { setIsSubmitting(false) } } return ( <form onSubmit={handleSubmit} className="space-y-6"> <div> <label htmlFor="name" className="block text-sm font-medium text-gray-700"> Imię i nazwisko </label> <input type="text" id="name" value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" required /> </div> {/* Pozostałe pola... */} <button type="submit" disabled={isSubmitting} className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50" > {isSubmitting ? 'Wysyłanie...' : 'Wyślij wiadomość'} </button> </form> ) }
3. Migracja API endpoints
// app/api/contact/route.ts import { NextRequest, NextResponse } from 'next/server' import { z } from 'zod' const contactSchema = z.object({ name: z.string().min(2, 'Imię musi mieć co najmniej 2 znaki'), email: z.string().email('Nieprawidłowy adres email'), message: z.string().min(10, 'Wiadomość musi mieć co najmniej 10 znaków'), }) export async function POST(request: NextRequest) { try { const body = await request.json() const validatedData = contactSchema.parse(body) // Integracja z zewnętrznym API (np. stara strona) const response = await fetch('https://old-api.domain.com/contact', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.OLD_API_TOKEN}`, }, body: JSON.stringify(validatedData), }) if (!response.ok) { throw new Error('Błąd API') } return NextResponse.json({ success: true }) } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( { error: 'Błąd walidacji', details: error.errors }, { status: 400 } ) } return NextResponse.json( { error: 'Błąd serwera' }, { status: 500 } ) } }
Faza 4: SEO i przekierowania
1. Mapowanie URL-i
// lib/redirects.ts export const redirectMap = { '/old-about': '/about', '/old-services': '/services', '/blog/old-post-1': '/articles/new-post-1', '/contact-us': '/contact', '/products/category-1': '/services/category-1', } // Middleware dla przekierowań // middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { redirectMap } from './lib/redirects' export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname // Sprawdź czy potrzebne przekierowanie if (redirectMap[pathname]) { return NextResponse.redirect( new URL(redirectMap[pathname], request.url), 301 ) } return NextResponse.next() } export const config = { matcher: [ '/((?!api|_next/static|_next/image|favicon.ico).*)', ], }
2. Sitemap i robots.txt
// app/sitemap.ts import { MetadataRoute } from 'next' export default function sitemap(): MetadataRoute.Sitemap { const baseUrl = 'https://twoja-nowa-strona.pl' // Statyczne strony const staticPages = [ '', '/about', '/services', '/contact', '/privacy-policy', ].map((route) => ({ url: `${baseUrl}${route}`, lastModified: new Date(), changeFrequency: 'monthly' as const, priority: route === '' ? 1 : 0.8, })) // Dynamiczne strony (np. blog) const articles = [ 'nextjs-vs-wordpress-2025', 'core-web-vitals-nextjs', // ... inne artykuły ].map((slug) => ({ url: `${baseUrl}/articles/${slug}`, lastModified: new Date(), changeFrequency: 'weekly' as const, priority: 0.6, })) return [...staticPages, ...articles] } // app/robots.ts import { MetadataRoute } from 'next' export default function robots(): MetadataRoute.Robots { return { rules: { userAgent: '*', allow: '/', disallow: ['/admin/', '/api/'], }, sitemap: 'https://twoja-nowa-strona.pl/sitemap.xml', } }
3. Metadata i Open Graph
// app/layout.tsx import type { Metadata } from "next"; export const metadata: Metadata = { title: { default: "Twoja Firma - Profesjonalne strony internetowe", template: "%s | Twoja Firma", }, description: "Tworzymy nowoczesne strony internetowe w Next.js. Szybkie, bezpieczne i zoptymalizowane pod SEO.", keywords: ["strony internetowe", "Next.js", "React", "SEO"], authors: [{ name: "Adam Noszczyński" }], creator: "Adam Noszczyński", metadataBase: new URL("https://twoja-nowa-strona.pl"), openGraph: { type: "website", locale: "pl_PL", url: "https://twoja-nowa-strona.pl", title: "Twoja Firma - Profesjonalne strony internetowe", description: "Tworzymy nowoczesne strony internetowe w Next.js.", siteName: "Twoja Firma", images: [ { url: "/og-image.jpg", width: 1200, height: 630, alt: "Twoja Firma", }, ], }, twitter: { card: "summary_large_image", title: "Twoja Firma", description: "Profesjonalne strony internetowe w Next.js", images: ["/og-image.jpg"], }, robots: { index: true, follow: true, googleBot: { index: true, follow: true, "max-video-preview": -1, "max-image-preview": "large", "max-snippet": -1, }, }, };
Faza 5: Testing i deployment
1. Testy przed wdrożeniem
// tests/migration.test.js const { chromium } = require("playwright"); describe("Migration Tests", () => { let browser, page; beforeAll(async () => { browser = await chromium.launch(); page = await browser.newPage(); }); test("All critical pages load correctly", async () => { const criticalPages = ["/", "/about", "/services", "/contact"]; for (const url of criticalPages) { const response = await page.goto(`http://localhost:3000${url}`); expect(response.status()).toBe(200); // Check for critical elements await expect(page.locator("h1")).toBeVisible(); await expect(page.locator("nav")).toBeVisible(); } }); test("Redirects work correctly", async () => { const response = await page.goto("http://localhost:3000/old-about"); expect(response.url()).toContain("/about"); }); test("Forms submit correctly", async () => { await page.goto("http://localhost:3000/contact"); await page.fill('input[name="name"]', "Test User"); await page.fill('input[name="email"]', "test@example.com"); await page.fill('textarea[name="message"]', "Test message"); await page.click('button[type="submit"]'); await expect(page).toHaveURL(/thank-you/); }); afterAll(async () => { await browser.close(); }); });
2. Performance testing
# Lighthouse CI npm install -g @lhci/cli # .lighthouserc.js module.exports = { ci: { collect: { url: [ 'http://localhost:3000/', 'http://localhost:3000/about', 'http://localhost:3000/services', ], numberOfRuns: 3, }, assert: { assertions: { 'categories:performance': ['error', {minScore: 0.9}], 'categories:accessibility': ['error', {minScore: 0.9}], 'categories:best-practices': ['error', {minScore: 0.9}], 'categories:seo': ['error', {minScore: 0.9}], }, }, }, }; # Uruchom testy lhci autorun
Faza 6: Go-live strategy
1. Blue-Green deployment
# .github/workflows/deploy.yml name: Deploy to Production on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: "18" cache: "npm" - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Build application run: npm run build - name: Deploy to Vercel uses: amondnet/vercel-action@v20 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.ORG_ID }} vercel-project-id: ${{ secrets.PROJECT_ID }} vercel-args: "--prod"
2. Monitoring po wdrożeniu
// lib/monitoring.ts export function setupMonitoring() { // Real User Monitoring if (typeof window !== 'undefined') { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(sendToAnalytics); getFID(sendToAnalytics); getFCP(sendToAnalytics); getLCP(sendToAnalytics); getTTFB(sendToAnalytics); }); } } function sendToAnalytics(metric: any) { // Wyślij do Google Analytics 4 if (typeof gtag !== 'undefined') { gtag('event', metric.name, { event_category: 'Web Vitals', event_label: metric.id, value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value), non_interaction: true, }); } }
Checklist migracji
✅ Pre-migration
- [ ] Backup starej strony
- [ ] Audyt SEO i performance
- [ ] Mapowanie URL-i
- [ ] Plan przekierowań
- [ ] Setup monitoringu
✅ During migration
- [ ] Komponenty core
- [ ] Routing i nawigacja
- [ ] Formularze i API
- [ ] SEO metadata
- [ ] Testing na staging
✅ Post-migration
- [ ] Monitoring 404 errors
- [ ] Google Search Console
- [ ] Performance tracking
- [ ] User feedback
- [ ] Backup strategy
Wyniki migracji - case study
Metryki przed i po:
| Metryka | Przed | Po | Poprawa | | ---------------- | ------ | ------ | ------- | | Load Time | 3.2s | 1.2s | 62% | | Lighthouse | 67 | 94 | +27 | | Bounce Rate | 45% | 28% | 38% | | Conversion | 2.3% | 2.8% | 23% | | Server Costs | $200/m | $120/m | 40% |
ROI migracji:
- Koszt migracji: $15,000
- Oszczędności roczne: $1,200 (hosting + maintenance)
- Wzrost konwersji: +$8,000/rok
- ROI: 61% w pierwszym roku
Podsumowanie
Migracja do Next.js to inwestycja, która się opłaca - 60% poprawa wydajności, lepsze SEO i pozycjonowanie, niższe koszty utrzymania, nowoczesna technologia oraz lepsza UX i konwersja.
Kluczem do sukcesu jest systematyczne podejście, dokładne planowanie i monitoring na każdym etapie.
Potrzebujesz pomocy z migracją? Skontaktuj się z nami - przeprowadzimy Twoją stronę do Next.js bez przestojów i utraty SEO.
Tagi:
Gotowy na start swojego projektu?
Skontaktuj się ze mną, aby omówić Twoje potrzeby i otrzymać bezpłatną konsultację.
