PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Next.js for the PHP developer – SSR, Server Components, GraphQL with Magento

by Henryk Tews / Tuesday, 27 February 2024 / Published in JavaScript

Next.js is the most popular React framework for building full-stack web applications with server-side rendering. For a PHP developer the concepts of SSR, static generation, and server-side data fetching are familiar – they are what PHP has always done. What is new is the React component model and the hybrid approach where some code runs on the server and some in the browser. I show Next.js from a PHP perspective with Magento 2 GraphQL as the backend.

Mental model for a PHP developer

PHP/Magento concept Next.js equivalent
PHP template (PHTML) React Server Component
JavaScript/Alpine.js block Client Component (with ‘use client’)
Controller + view page.tsx + layout.tsx
API endpoint route.ts (App Router API)
Session/cookie Server-side cookies via next/headers
getParam() from URL params and searchParams props
require() import (ES modules)

Server Components – PHP-like rendering

// app/products/[slug]/page.tsx
// This is a Server Component - runs on the server, like PHP
// No bundle size impact, can access databases/APIs directly

import { getMagentoProduct } from '@/lib/magento';

// Props receive URL params automatically - like $_GET in PHP
interface PageProps {
    params: { slug: string };
    searchParams: { store?: string };
}

export default async function ProductPage({ params, searchParams }: PageProps) {
    // Await data directly - no useEffect, no loading states
    // Runs on server, like a PHP controller
    const product = await getMagentoProduct(params.slug, searchParams.store ?? 'default');

    if (!product) {
        return <div>Product not found</div>;
    }

    return (
        <div className="max-w-5xl mx-auto px-4 py-8">
            <h1 className="text-3xl font-bold">{product.name}</h1>
            <p className="text-2xl text-primary mt-2">{product.price.regularPrice.amount.value} PLN</p>
            <div dangerouslySetInnerHTML={{ __html: product.description.html }} />

            {/* Client Component for interactive add-to-cart */}
            <AddToCartButton productId={product.id} sku={product.sku} />
        </div>
    );
}

// SEO metadata - generated on server, like PHP head block
export async function generateMetadata({ params }: PageProps) {
    const product = await getMagentoProduct(params.slug);
    return {
        title: product?.name ?? 'Product',
        description: product?.meta_description ?? '',
    };
}

Client Component – interactive parts only

// components/AddToCartButton.tsx
// 'use client' marks this as a Client Component
// This is like an Alpine.js x-data block - only interactive parts need to be client-side

'use client';

import { useState } from 'react';

interface Props {
    productId: number;
    sku: string;
}

export function AddToCartButton({ productId, sku }: Props) {
    const [qty,     setQty]    = useState(1);
    const [adding,  setAdding] = useState(false);
    const [added,   setAdded]  = useState(false);

    const addToCart = async () => {
        setAdding(true);
        try {
            const res = await fetch('/api/cart/add', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ sku, qty }),
            });
            if (res.ok) {
                setAdded(true);
                setTimeout(() => setAdded(false), 2000);
            }
        } finally {
            setAdding(false);
        }
    };

    return (
        <div className="flex gap-4 mt-6">
            <div className="flex items-center border rounded">
                <button onClick={() => setQty(q => Math.max(1, q - 1))} className="px-3 py-2">-</button>
                <span className="px-4">{qty}</span>
                <button onClick={() => setQty(q => q + 1)} className="px-3 py-2">+</button>
            </div>
            <button
                onClick={addToCart}
                disabled={adding}
                className={`px-8 py-2 rounded text-white ${added ? 'bg-green-500' : 'bg-blue-600'}`}
            >
                {adding ? 'Adding...' : added ? 'Added ✓' : 'Add to cart'}
            </button>
        </div>
    );
}

Fetching from Magento GraphQL

// lib/magento.ts
const MAGENTO_GRAPHQL_URL = process.env.NEXT_PUBLIC_MAGENTO_URL + '/graphql';

export async function getMagentoProduct(slug: string, storeCode = 'default') {
    const res = await fetch(MAGENTO_GRAPHQL_URL, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Store': storeCode,
        },
        body: JSON.stringify({
            query: `
                query GetProduct($urlKey: String!) {
                    products(filter: { url_key: { eq: $urlKey } }) {
                        items {
                            id
                            sku
                            name
                            meta_description
                            description { html }
                            price_range {
                                minimum_price {
                                    regular_price { value currency }
                                    final_price   { value currency }
                                }
                            }
                            media_gallery {
                                url
                                label
                            }
                        }
                    }
                }
            `,
            variables: { urlKey: slug },
        }),
        // Next.js caching - cache product for 60 seconds, then revalidate
        next: { revalidate: 60 },
    });

    const data = await res.json();
    return data?.data?.products?.items?.[0] ?? null;
}

Summary

Next.js App Router with Server Components is conceptually closer to PHP than client-side React. Data fetching happens on the server, HTML is generated server-side, and only interactive parts (add to cart, quantity selector) ship as client-side JavaScript. This hybrid approach gives the SEO benefits of SSR with the interactivity of a SPA where needed. For Magento 2 headless projects, Next.js + GraphQL is a well-established combination with good community support.

About Henryk Tews

What you can read next

TypeScript for the PHP developer – types, interfaces, generics, async/await
React.js – JSX, useState, useEffect, custom hooks, comparison with Vue
Vue.js for the PHP developer – Options API, Composition API, Magento REST communication

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}