> ## Documentation Index
> Fetch the complete documentation index at: https://docs.replo.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Dynamic Pages

> Build one page that fills in every item in your catalog.

export const TryPromptButton = ({prompt, imageSrc, imageAlt = "Template preview", imageStyles = {}, buttonCta = "Build in Replo"}) => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const PUBLISHER_API_BASE_URL = "https://publisher.replo.app";
  const APP_URL = "https://dashboard.replo.app";
  const LoaderIcon = <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
    animation: "spin 1s linear infinite"
  }}>
      <path d="M21 12a9 9 0 1 1-6.219-8.56" />
    </svg>;
  const ChevronIcon = <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="m9 18 6-6-6-6" />
    </svg>;
  async function postJSON(url, body, headers = {}, timeoutMs = 120000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...headers
        },
        body: JSON.stringify(body ?? ({})),
        signal: controller.signal
      });
      const responseText = await response.text();
      if (!response.ok) throw new Error(`API Error: ${response.status}`);
      return responseText ? JSON.parse(responseText) : {};
    } finally {
      clearTimeout(timeoutId);
    }
  }
  async function handleTryPrompt(event) {
    event.preventDefault();
    event.stopPropagation();
    setIsLoading(true);
    setError(null);
    try {
      const body = {
        prompt,
        file: null
      };
      const {seed} = await postJSON(`${PUBLISHER_API_BASE_URL}/api/v1/marketing/issue-marketing-site-jwt`, body);
      if (!seed) throw new Error("No seed returned from API");
      const url = new URL(APP_URL);
      url.searchParams.set("type", "agent");
      url.hash = `seed=${encodeURIComponent(seed)}`;
      window.open(url.toString(), "_blank");
    } catch (caughtError) {
      console.error("Failed to generate prompt:", caughtError);
      setError("Something went wrong. Please try again.");
    } finally {
      setIsLoading(false);
    }
  }
  return <div style={{
    position: "relative",
    display: "inline-block",
    width: "100%"
  }}>
      {}
      {imageSrc && <img src={imageSrc} alt={imageAlt} style={{
    width: "100%",
    height: "auto",
    display: "block",
    borderRadius: "8px",
    opacity: 0.8,
    ...imageStyles
  }} />}

      {}
      <div style={imageSrc ? {
    position: "absolute",
    top: "50%",
    left: "50%",
    transform: "translate(-50%, -50%)",
    zIndex: 10
  } : {
    display: "flex",
    justifyContent: "flex-start",
    margin: "1.25rem 0"
  }}>
        <button type="button" className="try-replo-btn" onClick={handleTryPrompt} disabled={isLoading} style={{
    backgroundColor: "#274AE2",
    color: "#ffffff",
    padding: "0.5rem 1.1rem",
    fontSize: "0.875rem",
    fontWeight: 600,
    fontFamily: "inherit",
    border: "none",
    borderRadius: "9999px",
    cursor: isLoading ? "not-allowed" : "pointer",
    lineHeight: 1.4,
    whiteSpace: "nowrap",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    WebkitFontSmoothing: "antialiased",
    boxSizing: "border-box",
    opacity: isLoading ? 0.7 : 1,
    boxShadow: "0 1px 2px rgba(15, 23, 42, 0.08)",
    transition: "background-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease"
  }}>
          <span style={{
    visibility: isLoading ? "hidden" : "visible",
    display: "inline-flex",
    alignItems: "center",
    gap: "0.375rem"
  }}>
            {buttonCta}
            {ChevronIcon}
          </span>
          {isLoading && <span style={{
    position: "absolute",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center"
  }}>
              {LoaderIcon}
            </span>}
        </button>
      </div>

      {}
      <style>
        {`
          @keyframes spin {
            from {
              transform: rotate(0deg);
            }
            to {
              transform: rotate(360deg);
            }
          }
          .try-replo-btn:hover:not(:disabled) {
            background-color: #1f3ec0 !important;
            box-shadow: 0 4px 12px rgba(39, 74, 226, 0.35);
            transform: translateY(-1px);
          }
          .try-replo-btn:active:not(:disabled) {
            transform: translateY(0);
            box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
          }
          .try-replo-btn:focus-visible {
            outline: 2px solid #274AE2;
            outline-offset: 2px;
          }
        `}
      </style>

      {}
      {error && <div style={{
    marginTop: "12px",
    padding: "12px 16px",
    backgroundColor: "#fee",
    border: "1px solid #fcc",
    borderRadius: "6px",
    color: "#c33",
    fontSize: "14px",
    textAlign: "center"
  }} role="alert">
          {error}
        </div>}
    </div>;
};

A dynamic page is one layout that fills in a different item (e.g. product, collection, post, etc) based on the URL. Edit the template once and every matching page updates together.

<Note>
  Dynamic pages live on your Replo site (a subdomain or `replosites.com`). They are not Theme Builder product templates, which publish into your Shopify theme at `yourstore.com/products/...`. If you need the page inside the theme, use the [Replo Theme Builder](/getting-started/sites-vs-themes).
</Note>

## Create a dynamic page

Ask Replo in chat. Say you want a dynamic page for every item (e.g. product, post, etc)

```text theme={null}
Build a dynamic product page template that works for every product in my store. Use my catalog for title, price, images, and variants.
```

<TryPromptButton prompt="Build a product page template that works for every product in my store. Use my catalog for title, price, images, and variants." />

If you already like a page you built for one item, ask Replo to reuse that layout as the template:

```text theme={null}
Use the layout from my existing page as the template for the whole catalog.
```

<TryPromptButton prompt="Use the layout from my existing page as the template for the whole catalog." />

You can tell it worked by the **Dynamic page** icon on the page card and in the breadcrumb. The route might look something like `/products/[slug]` - that path is the template, not a the URL that a visitor would see. Visitors land on `/products/blue-hat`, `/products/candle`, etc. The `blue-hat` or `candle` is an identifier which allows the dynamic page to show the correct item.

## Preview with a real item (one product, collection, post, etc)

Open the template in the [Site Builder](/apps/website-builder). A **Previewing** toolbar appears above the page. Click it, search for a item, and the preview jumps to that item's actual page.

Some types of dynamic content also have a **Placeholder** option that uses sample data. This can be useful if you haven't connected an integration which supplies the dynamic data yet. Make sure to connect the real dynamic data and preview before publishing.

For a page built from more than dynamic URL placeholder, choose each placeholder's value in turn, left to right. If Replo can't detect what fills the URL, it shows a form so you can type the exact URL path you want to preview.

## Edit once for the whole catalog

Mention the page in chat ("update my product page") or use [select mode](/features/edit-mode) on the template. Layout, copy, and design changes apply to every dynamic item that uses the template.

A few cases stay separate:

* A standalone page at a specific product URL (for example, a direct page which doesn't have `[slug]`, like `/products/house-blend`) overrides the template for that item only.
* Blog-style content collections keep layout on the template and body copy on each post.
* [Site Components](/features/site-components) such as the header still edit on their own, and are shared for every dynamic item - a change there applies everywhere that component is used.

Any updates to dynamic items (e.g. price changes in products, etc) automatically sync into the dynamic templte. You do not re-edit the page for those.

## Publish

[Publish](/publishing) the site as usual. The template appears as one row in the publish list (e.g. `/products/[slug]`). After publish, each item gets its own live URL from that one page - the path with square brackets (e.g, `[slug]`) is not a public URL.

## Other dynamic pages

The same pattern works for any dynamic content from any integration. Ask Replo to build a product collection page template, a blog template, a template for items from your Contentful integration, etc - the workflow is the same.

## FAQ

<AccordionGroup>
  <Accordion title="I'm using Shopify. Is this the same as a Shopify Theme Builder product template?">
    No. Theme Builder product templates publish into your Shopify theme and show up at `yourstore.com/products/...`. Dynamic pages publish to your Replo site, on a subdomain or `replosites.com`, and stay outside the theme. See [Start with Replo Agent](/getting-started/sites-vs-themes).
  </Accordion>

  <Accordion title="Do I need a separate page for each dynamic item?">
    No. One dynamic item page covers the catalog. Build a standalone page only when one item needs a completely different layout.
  </Accordion>

  <Accordion title="Why does the preview say Sample product?">
    You are on **Placeholder**, which uses sample data. Pick a real product in the **Previewing** toolbar.
  </Accordion>

  <Accordion title="Why doesn't editing one dynamic page update the others?">
    That page is probably a standalone page for one product, not a dynamic template. Look for the **Dynamic page** icon. If it is missing, ask Replo Agent to build a dyanmic page template for the whole catalog, or to reuse that page's layout as the template.
  </Accordion>

  <Accordion title="Can I still customize one dynamic item?">
    Yes. Ask Replo to give that item its own page. That standalone page overrides the template for that URL only.
  </Accordion>

  <Accordion title="Is this the same as Site Components?">
    No. [Site Components](/features/site-components) are shared pieces (header, footer, a section) that you drop onto many pages. A dynamic page is the page itself: one product-page layout that fills in each product from the URL.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Site Builder" href="/apps/website-builder">
    Preview the template with a real product, then review it before you publish.
  </Card>

  <Card title="Site Components" href="/features/site-components">
    Reuse a header, footer, or section across pages.
  </Card>

  <Card title="Selling with Shopify" href="/commerce/shopify">
    Connect your catalog so the template fills in live product data.
  </Card>

  <Card title="Publishing" href="/publishing">
    Put the template live so every product URL updates together.
  </Card>
</CardGroup>
