> ## Documentation Index
> Fetch the complete documentation index at: https://blog.mapptech.com.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Web Rendering

> Rendering strategies in web development have evolved, offering different trade-offs between performance, SEO, interactivity, and complexity. In this guide, we explore the key differences between Multi-Page Applications (MPA), Single-Page Applications (SPA), SPA-influenced Isomorphic approaches, and MPA influenced Split Execution with visuals, code examples, and diagrams.

## Multi-Page Applications (MPA)

<Note>
  MPAs reload the page on every navigation.
</Note>

<img className="block h-320 dark:hidden" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/mpa_light.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=0b749ce800477cc3092025f63e6d8bf1" alt="MPA Light Diagram" width="1848" height="1702" data-path="images/mpa_light.png" />

<img className="hidden h-320 dark:block" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/mpa_dark.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=2bdda336d8e50172848ef5658621f9d2" alt="MPA Dark Diagram" width="1848" height="1702" data-path="images/mpa_dark.png" />

**Pros:**

* Simple architecture.
* Full HTML is delivered on first load.
* When pages are mostly static, they can be effectively cached and delivered by a CDN.

**Cons:**

* Navigation is slower due to full page reloads.
* Dynamic updates require complete page refreshes.

***

## Single-Page Applications (SPA)

<Note>
  SPAs load content dynamically on the client. This means the initial HTML is essentially empty.
</Note>

<Tabs>
  <Tab title="Diagram">
    <img className="block h-320 dark:hidden" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/spa_light.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=c99fb80e866f08f7b10b74f1f8236131" alt="SPA Light Diagram" width="1852" height="1979" data-path="images/spa_light.png" />

    <img className="hidden h-320 dark:block" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/spa_dark.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=8b9336f7c0071bf29300886b255275d2" alt="SPA Dark Diagram" width="1852" height="1979" data-path="images/spa_dark.png" />
  </Tab>

  <Tab title="React Diagram">
    <img className="block h-320 dark:hidden" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/react_vanilla_light.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=c88d13c4711889b7d6bac7240fc09e72" alt="React Vanilla Light Diagram" width="3242" height="708" data-path="images/react_vanilla_light.png" />

    <img className="hidden h-320 dark:block" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/react_vanilla_dark.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=f39526c13b618db5c0fc3afaa648def3" alt="React Vanilla Dark Diagram" width="3242" height="708" data-path="images/react_vanilla_dark.png" />
  </Tab>

  <Tab title="Code">
    <CodeGroup>
      ```jsx tutorial-code.jsx theme={null}
      function App() {
        const [data, setData] = React.useState(null);
        const [query, setQuery] = React.useState("default");
        const [input, setInput] = React.useState("default");
        const [loading, setLoading] = React.useState(true);

        React.useEffect(() => {
          setLoading(true);
          fetch(
            `https://api.example.com/data?query=${encodeURIComponent(query)}`
          )
            .then((response) => response.json())
            .then((result) => {
              setData(result);
              setLoading(false);
            })
            .catch((error) => {
              console.error(error);
              setLoading(false);
            });
        }, [query]);

        const handleSubmit = (e) => {
          e.preventDefault();
          setQuery(input);
        };

        return (
          <div>
            <h1>Data Fetch Tutorial</h1>
            <form onSubmit={handleSubmit}>
              <input
                value={input}
                onChange={(e) => setInput(e.target.value)}
                placeholder="Enter query"
              />
              <button type="submit">Search</button>
            </form>
            {loading ? (
              <p>Loading data...</p>
            ) : (
              <pre>{JSON.stringify(data, null, 2)}</pre>
            )}
          </div>
        );
      }
      ```

      ```jsx actual-code.jsx theme={null}
      const DataContext = React.createContext();

      function DataProvider({ children }) {
        const [data, setData] = React.useState(null);
        const [loading, setLoading] = React.useState(false);
        const [error, setError] = React.useState(null);
        const [query, setQuery] = React.useState("default");

        React.useEffect(() => {
          const controller = new AbortController();
          const signal = controller.signal;

          async function fetchData() {
            setLoading(true);
            setError(null);
            try {
              const response = await fetch(
                `https://api.example.com/data?query=${encodeURIComponent(query)}`,
                { signal }
              );
              if (!response.ok) throw new Error("Network response was not ok");
              const result = await response.json();
              const processedData = result.map((item) => ({
                ...item,
                processed: true,
              }));
              setData(processedData);
            } catch (err) {
              if (err.name !== "AbortError") setError(err);
            } finally {
              setLoading(false);
            }
          }
          fetchData();
          return () => controller.abort();
        }, [query]);

        return (
          <DataContext.Provider value={{ data, loading, error, query, setQuery }}>
            {children}
          </DataContext.Provider>
        );
      }

      function DisplayData() {
        const { data, loading, error, query, setQuery } = React.useContext(
          DataContext
        );
        const [input, setInput] = React.useState(query);

        const handleSubmit = (e) => {
          e.preventDefault();
          setQuery(input);
        };

        return (
          <div>
            <form onSubmit={handleSubmit}>
              <input
                value={input}
                onChange={(e) => setInput(e.target.value)}
                placeholder="Enter query"
              />
              <button type="submit">Search</button>
            </form>
            {loading && <p>Loading data...</p>}
            {error && <p>Error: {error.message}</p>}
            {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
          </div>
        );
      }

      function App() {
        return (
          <DataProvider>
            <h1>Advanced SPA Data Fetching Example</h1>
            <DisplayData />
          </DataProvider>
        );
      }
      ```

      ```jsx react-query.jsx theme={null}
      function DataComponent({ query }) {
        const { data, isLoading, error } = useQuery(
          ["fetchData", query],
          () =>
            fetch(
              `https://api.example.com/data?query=${encodeURIComponent(query)}`
            ).then((res) => res.json())
        );
        if (isLoading) return <p>Loading with React Query...</p>;
        if (error) return <p>Error loading data</p>;
        return <pre>{JSON.stringify(data, null, 2)}</pre>;
      }

      function App() {
        const [query, setQuery] = React.useState("default");
        const [input, setInput] = React.useState("default");

        const handleSubmit = (e) => {
          e.preventDefault();
          setQuery(input);
        };

        return (
          <div>
            <h1>SPA using React Query</h1>
            <form onSubmit={handleSubmit}>
              <input
                value={input}
                onChange={(e) => setInput(e.target.value)}
                placeholder="Enter query"
              />
              <button type="submit">Search</button>
            </form>
            <DataComponent query={query} />
          </div>
        );
      }
      ```
    </CodeGroup>
  </Tab>
</Tabs>

**Pros:**

* Fast in-page navigation.
* Dynamic interactivity.

**Cons:**

* Minimal initial HTML.
* Heavy reliance on client-side JavaScript.

***

## SPA-influenced Isomorphic

<Note>
  Initial HTML is rendered on the server for SEO and fast first paint, then hydrated on the client to enable interactivity.
</Note>

<Tabs>
  <Tab title="Diagram">
    <img className="block h-320 dark:hidden" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/spa_iso_light.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=796f39e315c89491537a7eb11f473da1" alt="SPA ISO Light Diagram" width="2591" height="2046" data-path="images/spa_iso_light.png" />

    <img className="hidden h-320 dark:block" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/spa_iso_dark.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=b34b35a1b9c9b15a4c19b652a9a0e3ac" alt="SPA ISO Dark Diagram" width="2591" height="2046" data-path="images/spa_iso_dark.png" />
  </Tab>

  <Tab title="React Diagram">
    <img className="block h-320 dark:hidden" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/react_skeleton_light.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=17b5a847f4579555df97da4a047a4995" alt="React Skeleton Light Diagram" width="3242" height="708" data-path="images/react_skeleton_light.png" />

    <img className="hidden h-320 dark:block" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/react_skeleton_dark.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=3221aea2571144eaf84c318810d75997" alt="React Skeleton Dark Diagram" width="3242" height="708" data-path="images/react_skeleton_dark.png" />
  </Tab>

  <Tab title="Code">
    <CodeGroup>
      ```jsx page.jsx theme={null}
      export const metadata = {
        title: "SPA-influenced Isomorphic Rendering Example",
        description: "An isomorphic rendering example with SEO metadata.",
        keywords: "rendering, isomorphic, react, SEO, server components",
      };

      // Assume MutationManager is dynamically imported for client-side behavior.
      export default async function Page({ searchParams }) {
        const query = searchParams.query || "default";
        const res = await fetch(
          `https://api.example.com/data?query=${encodeURIComponent(query)}`,
          { cache: "force-cache" }
        );
        const data = await res.json();
        return (
          <div>
            <h1>Isomorphic Page - Results for: {query}</h1>
            <pre>{JSON.stringify(data, null, 2)}</pre>
            <MutationManager />
          </div>
        );
      }
      ```

      ```jsx loading.jsx theme={null}
      export default function Loading() {
        return <p>Loading...</p>;
      }
      ```

      ```jsx error.jsx theme={null}
      "use client";
      export default function Error({ error, reset }) {
        return (
          <div>
            <h1>Error Loading Page</h1>
            <p>{error.message}</p>
            <button onClick={reset}>Try again</button>
          </div>
        );
      }
      ```

      ```jsx MutationManager.jsx theme={null}
      "use client";
      import { useState } from "react";
      import { useMutation, useQueryClient } from "react-query";

      async function updateData(newValue) {
        const res = await fetch("/api/update", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ value: newValue }),
        });
        if (!res.ok) throw new Error("Update failed");
        return res.json();
      }

      export default function MutationManager() {
        const [input, setInput] = useState("");
        const queryClient = useQueryClient();
        const mutation = useMutation(updateData, {
          onSuccess: () => queryClient.invalidateQueries("fetchData"),
        });

        const handleSubmit = (e) => {
          e.preventDefault();
          mutation.mutate(input);
        };

        if (mutation.error) {
          return (
            <div>
              <p>Error updating data.</p>
            </div>
          );
        }

        if (mutation.isLoading) {
          return (
            <div>
              <p>Updating...</p>
            </div>
          );
        }

        return (
          <div>
            <form onSubmit={handleSubmit}>
              <input
                value={input}
                onChange={(e) => setInput(e.target.value)}
                placeholder="Enter new value"
              />
              <button type="submit">Submit</button>
            </form>
          </div>
        );
      }
      ```
    </CodeGroup>
  </Tab>
</Tabs>

**Pros:**

* SEO-friendly full HTML is delivered initially.

**Cons:**

* Requires coordination between client and server.
* More complex setup.

***

## MPA-influenced Split Execution

<Note>
  Mostly/Fully HTML rendered on the server using server actions.
</Note>

<Tabs>
  <Tab title="Diagram">
    <img className="block h-320 dark:hidden" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/mpa_split_light.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=dfe1f4450d48c9ae9e97676319c6bdfa" alt="MPA Split Light Diagram" width="2406" height="1714" data-path="images/mpa_split_light.png" />

    <img className="hidden h-320 dark:block" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/mpa_split_dark.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=6d0c220e055561db358e7dafea9b7715" alt="MPA Split Dark Diagram" width="2406" height="1714" data-path="images/mpa_split_dark.png" />
  </Tab>

  <Tab title="React Diagram">
    <AccordionGroup>
      <Accordion title="React Server Diagram" icon="server">
        <img className="block h-320 dark:hidden" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/react_server_light.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=4bd3c4b778bd2fc8c021ecb870ee2cc7" alt="React Server Light Diagram" width="3242" height="708" data-path="images/react_server_light.png" />

        <img className="hidden h-320 dark:block" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/react_server_dark.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=9f1c3998ddaf4bf4c2b1b44c00d7a353" alt="React Server Dark Diagram" width="3242" height="708" data-path="images/react_server_dark.png" />
      </Accordion>

      <Accordion title="React Lean Diagram" icon="code">
        <img className="block h-320 dark:hidden" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/react_lean_light.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=0aa02e392a4a13f9e2898ee8836f8ce0" alt="React Lean Light Diagram" width="3242" height="708" data-path="images/react_lean_light.png" />

        <img className="hidden h-320 dark:block" src="https://mintcdn.com/mapptech/gm742MzjC2-MV-lv/images/react_lean_dark.png?fit=max&auto=format&n=gm742MzjC2-MV-lv&q=85&s=a6d1addcfef5f7ff3793d7095240bf23" alt="React Lean Dark Diagram" width="3242" height="708" data-path="images/react_lean_dark.png" />
      </Accordion>
    </AccordionGroup>
  </Tab>

  <Tab title="Code">
    <CodeGroup>
      ```jsx page.jsx theme={null}
      export const metadata = {
        title: "Server-Rendered Page with Actions",
        description:
          "A server-rendered page that handles mutations using server actions and revalidates paths.",
        keywords: "rendering, server actions, Next.js, MPA",
      };

      export default async function Page({ searchParams }) {
        const query = searchParams.query || "default";
        const res = await fetch(
          `https://api.example.com/data?query=${encodeURIComponent(query)}`,
          { cache: "force-cache" }
        );
        const data = await res.json();

        async function handleMutation(formData) {
          "use server";
          const res = await fetch("https://api.example.com/update", {
            method: "POST",
            body: formData,
          });
          revalidatePath("/");
        }

        return (
          <div>
            <h1>Server-Rendered Page with Actions</h1>
            <pre>{JSON.stringify(data, null, 2)}</pre>
            <ErrorBoundary fallback={<p>Error updating data.</p>}>
              <Suspense fallback={<p>Updating...</p>}>|
                <form action={handleMutation}>
                  <input name="value" type="text" />
                  <button type="submit">Submit</button>
                </form>
              </Suspense>
            </ErrorBoundary>
          </div>
        );
      }
      ```

      ```jsx loading.jsx theme={null}
      export default function Loading() {
        return <p>Loading...</p>;
      }
      ```

      ```jsx error.jsx theme={null}
      "use client";
      export default function Error({ error, reset }) {
        return (
          <div>
            <h1>Error Loading Page</h1>
            <p>{error.message}</p>
            <button onClick={reset}>Try again</button>
          </div>
        );
      }
      ```
    </CodeGroup>
  </Tab>
</Tabs>

**Pros:**

* CDN-friendly HTML delivery.
* Minimal client-side code.
* Excellent SEO.

**Cons:**

* Client-side interactivity may require additional handling.
* Increased server CPU consumption.

***

## 📊 Rendering Strategy Comparison

| Property                   | MPA | SPA | SPA Isomorphic | MPA Split Execution |
| -------------------------- | :-: | :-: | :------------: | :-----------------: |
| **SEO Friendly**           |  ✅  |  ❌  |        ✅       |          ✅          |
| **Fast Navigation**        |  ❌  |  ✅  |        ✅       |          ✅          |
| **Interactive Experience** |  ❌  |  ✅  |        ✅       |          ✅          |
| **CDN Friendly**           |  ✅  |  ❌  |        ✅       |          ✅          |
| **Simple Architecture**    |  ✅  |  ❌  |        ❌       |          ✅          |
| **Lightweight Client JS**  |  ✅  |  ❌  |        ✅       |          ✅          |
| **Low CPU Usage**          |  ❌  |  ✅  |        ✅       |          ❌          |
| **Low Server Costs**       |  ❌  |  ✅  |        ❌       |          ✅          |

***

## 🚀 Live Example: NextFaster

To see these rendering strategies in action, check out **[NextFaster](https://next-faster.vercel.app)** — a real-world project that blends server components, React islands, and split execution for optimal performance and SEO.

<div style={{ position: "relative", marginTop: "1.5rem", borderRadius: "12px", overflow: "hidden", border: "1px solid #ccc" }}>
  <div
    style={{
position: "absolute",
top: "0.5rem",
right: "0.5rem",
display: "flex",
gap: "0.25rem",
zIndex: 10
}}
  >
    <button
      onClick={() => window.open("https://next-faster.vercel.app", "_blank")}
      title="Open in new tab"
      style={{
    background: "white",
    border: "1px solid #eee",
    borderRadius: "6px",
    padding: "0.3rem",
    cursor: "pointer",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    boxShadow: "0 1px 3px rgba(0,0,0,0.05)"
  }}
    >
      <Icon icon="arrow-up-right-from-square" iconType="solid" color="#0070f3" />
    </button>

    <button
      onClick={() => window.open("https://github.com/ethanniser/NextFaster", "_blank")}
      title="View GitHub repo"
      style={{
    background: "white",
    border: "1px solid #eee",
    borderRadius: "6px",
    padding: "0.3rem",
    cursor: "pointer",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    boxShadow: "0 1px 3px rgba(0,0,0,0.05)"
  }}
    >
      <Icon icon="github" iconType="brands" color="#000000" />
    </button>
  </div>

  <iframe src="https://next-faster.vercel.app" width="100%" height="600px" style={{ border: "none", display: "block" }} />
</div>

**What makes NextFaster interesting?**

* ⚙️ **React Server Components** with minimal client-side JS
* 🔄 **Server Actions** and **Edge revalidation**
* 💸 **Exceptionally low hosting cost** while high performance
* 🔍 Excellent **SEO and performance** balance

> Ideal for studying modern rendering techniques in Next.js 14+ with App Router.

## 📺 Recommended Videos

<CardGroup cols={2}>
  <Card title="Comparing Data Fetching Strategies" icon="server" href="https://youtu.be/51pf_nCJpwg">
    <a href="https://youtu.be/51pf_nCJpwg" target="_blank" rel="noopener noreferrer" className="flex flex-col h-full">
      <img src="https://img.youtube.com/vi/51pf_nCJpwg/hqdefault.jpg" alt="Why I don't use React-Query and tRPC in Next.js" className="rounded-xl" noZoom />

      <div className="mt-auto">
        <Icon icon="youtube" iconType="brands" /> Watch on YouTube
      </div>
    </a>
  </Card>

  <Card title="Data Loading in Next.js 15" icon="download" href="https://youtu.be/nfTv6arvLZc">
    <a href="https://youtu.be/nfTv6arvLZc" target="_blank" rel="noopener noreferrer" className="flex flex-col h-full">
      <img src="https://img.youtube.com/vi/nfTv6arvLZc/hqdefault.jpg" alt="Data Loading in Next.js 15" className="rounded-xl" noZoom />

      <div className="mt-auto">
        <Icon icon="youtube" iconType="brands" /> Watch on YouTube
      </div>
    </a>
  </Card>

  <Card title="Why Server Components Matter" icon="compass" href="https://youtu.be/rGPpQdbDbwo">
    <a href="https://youtu.be/rGPpQdbDbwo" target="_blank" rel="noopener noreferrer" className="flex flex-col h-full">
      <img src="https://img.youtube.com/vi/rGPpQdbDbwo/hqdefault.jpg" alt="React Server Components Change Everything" className="rounded-xl" noZoom />

      <div className="mt-auto">
        <Icon icon="youtube" iconType="brands" /> Watch on YouTube
      </div>
    </a>
  </Card>

  <Card title="SSR vs CSR: SEO Pitfalls" icon="magnifying-glass" href="https://youtu.be/YfkJyRFvP1s">
    <a href="https://youtu.be/YfkJyRFvP1s" target="_blank" rel="noopener noreferrer" className="flex flex-col h-full">
      <img src="https://img.youtube.com/vi/YfkJyRFvP1s/hqdefault.jpg" alt="Server Side Rendering Vs. Client Side Rendering. SEO Pitfalls!" className="rounded-xl" noZoom />

      <div className="mt-auto">
        <Icon icon="youtube" iconType="brands" /> Watch on YouTube
      </div>
    </a>
  </Card>
</CardGroup>

## 📚 Further Reading

* [React Server Components](https://react.dev/reference/rsc/server-components)
* [React Query](https://react-query.tanstack.com/)
* [Next.js Documentation](https://nextjs.org/docs)
* [Server Actions in Next.js](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions)
* [React Query vs React Server Components](https://www.reddit.com/r/nextjs/comments/19d0sar/nextjs_14_server_actions_vs_react_query/)

## 🧠 Final Thoughts

Each rendering strategy has its strengths. MPAs are straightforward and, when mostly static, can be effectively cached via a CDN; SPAs deliver dynamic interactivity entirely on the client (resulting in minimal initial HTML); SPA-influenced Isomorphic approaches combine SEO and dynamic updates by server-rendering the initial HTML and hydrating on the client; and MPA influenced Split Execution offers performance with server-handled mutations using server actions.
