> ## 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.

# How to Build a Subscription Landing Page

> Subscription pages transform one-time buyers into recurring revenue. They increase LTV by 3-5x by converting customers to predictable, recurring orders. These pages explain benefits, simplify signup, and overcome subscription friction to maximize subscriber acquisition.

export const Device = ({prompt, desktopImageSource, mobileImageSource, imageAlt = "Template preview", buttonCta = "Build in Replo"}) => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [isMobile, setIsMobile] = useState(false);
  const [isDragging, setIsDragging] = useState(false);
  const [dragStart, setDragStart] = useState({
    x: 0,
    y: 0
  });
  const [scrollStart, setScrollStart] = useState({
    x: 0,
    y: 0
  });
  const [velocity, setVelocity] = useState({
    x: 0,
    y: 0
  });
  const [lastPos, setLastPos] = useState({
    x: 0,
    y: 0
  });
  const [lastTime, setLastTime] = useState(0);
  const [scrollContainer, setScrollContainer] = useState(null);
  const showToggle = desktopImageSource && mobileImageSource;
  const currentImage = showToggle && isMobile ? mobileImageSource : desktopImageSource;
  const handleMouseDown = (e, container) => {
    setIsDragging(true);
    setDragStart({
      x: e.clientX,
      y: e.clientY
    });
    setScrollStart({
      x: container.scrollLeft,
      y: container.scrollTop
    });
    setLastPos({
      x: e.clientX,
      y: e.clientY
    });
    setLastTime(Date.now());
    setVelocity({
      x: 0,
      y: 0
    });
    setScrollContainer(container);
    e.preventDefault();
  };
  const handleMouseMove = (e, container) => {
    if (!isDragging) return;
    const now = Date.now();
    const dt = now - lastTime;
    if (dt > 0) {
      const dx = e.clientX - dragStart.x;
      const dy = e.clientY - dragStart.y;
      const vx = (e.clientX - lastPos.x) / dt;
      const vy = (e.clientY - lastPos.y) / dt;
      container.scrollLeft = scrollStart.x - dx;
      container.scrollTop = scrollStart.y - dy;
      setVelocity({
        x: vx,
        y: vy
      });
      setLastPos({
        x: e.clientX,
        y: e.clientY
      });
      setLastTime(now);
    }
    e.preventDefault();
  };
  const handleMouseUp = () => {
    setIsDragging(false);
    if (scrollContainer && (Math.abs(velocity.x) > 0.1 || Math.abs(velocity.y) > 0.1)) {
      startMomentumScroll();
    }
  };
  const startMomentumScroll = () => {
    let currentVelocity = {
      ...velocity
    };
    const friction = 0.95;
    const minVelocity = 0.1;
    const animate = () => {
      if (!scrollContainer) return;
      scrollContainer.scrollLeft -= currentVelocity.x * 16;
      scrollContainer.scrollTop -= currentVelocity.y * 16;
      currentVelocity.x *= friction;
      currentVelocity.y *= friction;
      if (Math.abs(currentVelocity.x) > minVelocity || Math.abs(currentVelocity.y) > minVelocity) {
        requestAnimationFrame(animate);
      }
    };
    requestAnimationFrame(animate);
  };
  useEffect(() => {
    const handleGlobalMouseUp = () => {
      setIsDragging(false);
    };
    window.addEventListener("mouseup", handleGlobalMouseUp);
    return () => {
      window.removeEventListener("mouseup", handleGlobalMouseUp);
    };
  }, []);
  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="18" height="18" 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 ctrl = new AbortController();
    const t = setTimeout(() => ctrl.abort(), timeoutMs);
    try {
      const res = await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...headers
        },
        body: JSON.stringify(body ?? ({})),
        signal: ctrl.signal
      });
      const text = await res.text();
      if (!res.ok) throw new Error(`API Error: ${res.status}`);
      return text ? JSON.parse(text) : {};
    } finally {
      clearTimeout(t);
    }
  }
  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_SIGNUP_URL);
      url.hash = `seed=${encodeURIComponent(seed)}`;
      window.open(url.toString(), "_blank");
      setIsLoading(false);
    } catch (err) {
      console.error("Failed to generate prompt:", err);
      setError(err.message || "Something went wrong. Please try again.");
      setIsLoading(false);
    }
  }
  return <div className="device-wrapper" style={{
    width: "100%",
    display: "flex",
    flexDirection: "column",
    alignItems: "center",
    position: "relative",
    paddingTop: showToggle ? "16px" : "0"
  }}>
      {}
      {showToggle && <div style={{
    display: "flex",
    gap: "0.5rem",
    alignItems: "center",
    padding: "0.25rem",
    backgroundColor: "#f3f4f6",
    borderRadius: "9999px",
    position: "absolute",
    top: "0",
    left: "50%",
    transform: "translateX(-50%)",
    zIndex: 20
  }}>
          <button onClick={() => setIsMobile(false)} style={{
    padding: "0.375rem 0.75rem",
    fontSize: "0.8125rem",
    fontWeight: "500",
    fontFamily: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
    border: "none",
    borderRadius: "9999px",
    cursor: "pointer",
    backgroundColor: !isMobile ? "white" : "transparent",
    color: !isMobile ? "#111827" : "#6b7280",
    boxShadow: !isMobile ? "0 1px 3px 0 rgba(0, 0, 0, 0.1)" : "none",
    transition: "all 0.2s ease"
  }}>
            Desktop
          </button>
          <button onClick={() => setIsMobile(true)} style={{
    padding: "0.375rem 0.75rem",
    fontSize: "0.8125rem",
    fontWeight: "500",
    fontFamily: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
    border: "none",
    borderRadius: "9999px",
    cursor: "pointer",
    backgroundColor: isMobile ? "white" : "transparent",
    color: isMobile ? "#111827" : "#6b7280",
    boxShadow: isMobile ? "0 1px 3px 0 rgba(0, 0, 0, 0.1)" : "none",
    transition: "all 0.2s ease"
  }}>
            Mobile
          </button>
        </div>}

      {}
      <div style={{
    position: "relative",
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
    width: "100%"
  }} onMouseUp={handleMouseUp} onMouseLeave={handleMouseUp}>
        {isMobile && mobileImageSource ? <div className="device device-iphone-14-pro device-mobile-responsive" style={{
    transform: "scale(0.7)",
    transformOrigin: "center center"
  }}>
            <div className="device-frame">
              <div className="device-screen" style={{
    overflow: "auto",
    overflowX: "hidden",
    cursor: isDragging ? "grabbing" : "grab",
    userSelect: "none"
  }} onMouseDown={e => {
    const scrollContainer = e.currentTarget;
    handleMouseDown(e, scrollContainer);
  }} onMouseMove={e => {
    const scrollContainer = e.currentTarget;
    handleMouseMove(e, scrollContainer);
  }}>
                <img src={mobileImageSource} alt={imageAlt} loading="lazy" style={{
    width: "100%",
    height: "auto",
    display: "block",
    pointerEvents: "none"
  }} draggable="false" />
              </div>
            </div>
            <div className="device-stripe"></div>
            <div className="device-header"></div>
            <div className="device-sensors"></div>
            <div className="device-btns"></div>
            <div className="device-power"></div>
            <div className="device-home"></div>
          </div> : desktopImageSource ? <div className="device device-macbook-pro device-desktop-responsive" style={{
    transform: "scale(0.85)",
    transformOrigin: "center center"
  }}>
            <div className="device-frame">
              <div className="device-screen" style={{
    overflow: "auto",
    overflowX: "hidden",
    cursor: isDragging ? "grabbing" : "grab",
    userSelect: "none"
  }} onMouseDown={e => {
    const scrollContainer = e.currentTarget;
    handleMouseDown(e, scrollContainer);
  }} onMouseMove={e => {
    const scrollContainer = e.currentTarget;
    handleMouseMove(e, scrollContainer);
  }}>
                <img src={desktopImageSource} alt={imageAlt} loading="lazy" style={{
    width: "100%",
    height: "auto",
    display: "block",
    pointerEvents: "none"
  }} draggable="false" />
              </div>
            </div>
            <div className="device-stripe"></div>
            <div className="device-header"></div>
            <div className="device-sensors"></div>
            <div className="device-btns"></div>
            <div className="device-power"></div>
            <div className="device-home"></div>
          </div> : null}

        {}
        {currentImage && <div style={{
    position: "absolute",
    top: "50%",
    left: "50%",
    transform: "translate(-50%, -50%)",
    zIndex: 10
  }}>
            <button type="button" onClick={handleTryPrompt} disabled={isLoading} style={{
    backgroundColor: "rgb(39, 74, 226)",
    color: "white",
    padding: "0.75rem 1.5rem",
    fontSize: "1rem",
    fontWeight: "600",
    fontFamily: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
    border: "none",
    borderRadius: "9999px",
    cursor: isLoading ? "not-allowed" : "pointer",
    textAlign: "center",
    lineHeight: "1.5rem",
    whiteSpace: "nowrap",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    WebkitFontSmoothing: "antialiased",
    boxSizing: "border-box",
    opacity: isLoading ? 0.7 : 1,
    transition: "background-color 0.2s ease"
  }} onMouseEnter={e => {
    if (!isLoading) {
      e.currentTarget.style.backgroundColor = "rgb(59, 94, 246)";
    }
  }} onMouseLeave={e => {
    if (!isLoading) {
      e.currentTarget.style.backgroundColor = "rgb(39, 74, 226)";
    }
  }}>
              <span style={{
    visibility: isLoading ? "hidden" : "visible",
    display: "inline-flex",
    alignItems: "center",
    gap: "0.5rem"
  }}>
                {buttonCta}
                {ChevronIcon}
              </span>
              {isLoading && <span style={{
    position: "absolute",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center"
  }}>
                  {LoaderIcon}
                </span>}
            </button>
          </div>}
      </div>

      {}
      <style>
        {`
          @keyframes spin {
            from {
              transform: rotate(0deg);
            }
            to {
              transform: rotate(360deg);
            }
          }
          
          .device-screen {
            scrollbar-width: none; /* Firefox */
            -ms-overflow-style: none; /* IE and Edge */
          }
          
          .device-screen::-webkit-scrollbar {
            display: none; /* Chrome, Safari, Opera */
          }

          /* Desktop - compensate for scaling with negative margins */
          .device-mobile-responsive {
            margin-top: -80px;
            margin-bottom: -80px;
          }

          .device-desktop-responsive {
            margin-top: 16px;
            margin-bottom: 16px;
          }

          /* Mobile viewports - simplified with fixed height and clipping */
          @media (max-width: 768px) {
            .device-wrapper {
              overflow: hidden;
            }

            .device-mobile-responsive {
              transform: scale(0.5) !important;
              margin-top: -160px;
              margin-bottom: -160px;
            }

            .device-desktop-responsive {
              transform: scale(0.5) !important;
              margin-top: -60px;
              margin-bottom: -60px;
            }
          }
        `}
      </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>;
};

<Device
  prompt={`Recreate this subscription page for me: https://javvycoffee.com/pages/subscription. Adapt it to my brand and subscription program.

Structure:
- Hero section with the main value proposition and discount
- Benefits section explaining why customers should subscribe (savings, convenience, flexibility)
- "How It Works" section breaking down the 3-step signup process
- Featured products or product categories available for subscription
- FAQ section addressing common subscription concerns
- Customer testimonials or social proof

Use a clean, benefit-driven layout. Make the savings and convenience obvious immediately. Address subscription anxiety by emphasizing flexibility (skip, pause, cancel anytime).`}
  mobileImageSource="/images/use-cases/subscription/mobile.png"
  desktopImageSource="/images/use-cases/subscription/desktop.png"
  buttonCta="Build in Replo"
  imageAlt="Collab Collection Page Example"
/>

## Step 1: Start Your Subscription Page with a Prompt

### Starter prompt

The more specific you are about your subscription model, pricing, and benefits, the better the output.

```
Recreate this subscription page for me: https://javvycoffee.com/pages/subscription. Adapt it to my brand and subscription program.

Structure:
- Hero section with the main value proposition and discount
- Benefits section explaining why customers should subscribe (savings, convenience, flexibility)
- "How It Works" section breaking down the 3-step signup process
- Featured products or product categories available for subscription
- FAQ section addressing common subscription concerns
- Customer testimonials or social proof

Use a clean, benefit-driven layout. Make the savings and convenience obvious immediately. Address subscription anxiety by emphasizing flexibility (skip, pause, cancel anytime).
```

## Step 2: Customize Your Content

Replace the AI-generated content with your actual subscription details, pricing structure, and product lineup.

<AccordionGroup>
  <Accordion title="Update your subscription offer">
    Your subscription discount is the primary conversion lever. Most brands see optimal results between 15-25% off.

    ```
    Update the subscription offer:

    Discount: [X]% off every order
    Frequency options: Every [30/60/90] days
    Additional perks: [Free shipping, early access to new products, exclusive flavors]

    Hero headline: [Main value proposition]
    Subheadline: [Supporting benefit or social proof]
    ```

    <Tip>
      If your subscription discount is lower than competitors, lead with convenience benefits instead of savings. "Never run out" and "Automatic delivery" can drive conversion when the discount alone isn't compelling.
    </Tip>
  </Accordion>

  <Accordion title="Write your benefits section">
    Subscription pages convert when they overcome three objections: price anxiety, commitment fear, and inconvenience worry.

    ```
    List 3-4 key benefits:

    Benefit 1: [Best pricing / Biggest savings / VIP pricing]
    Description: [Explain the dollar value or percentage saved]

    Benefit 2: [Flexibility / Full control / No commitment]
    Description: [Emphasize ability to skip, pause, or cancel anytime]

    Benefit 3: [Convenience / Never run out / Automatic delivery]
    Description: [Explain how it saves time and ensures they always have product]

    Benefit 4 (optional): [Exclusive perks / Early access / Special offers]
    Description: [Subscriber-only benefits like new product launches or members-only sales]
    ```

    <Note>
      Order matters. If your discount is 20%+, lead with savings. If it's 10-15%, lead with convenience. Test benefit order to find what resonates with your audience.
    </Note>
  </Accordion>

  <Accordion title="Create your 'How It Works' section">
    Reduce friction by making the subscription process feel simple and reversible.

    ```
    Break down the subscription process into 3 simple steps:

    Step 1: [Select / Choose / Build]
    Description: [Pick your products and delivery frequency]

    Step 2: [Subscribe / Sign Up / Get Started]
    Description: [Create your subscription at checkout]

    Step 3: [Receive / Enjoy / Relax]
    Description: [Get automatic deliveries on your schedule]

    Add a note about management: "Change, skip, or cancel anytime from your account."
    ```
  </Accordion>

  <Accordion title="Add your product lineup">
    Show which products are available for subscription. If you offer subscriptions on everything, highlight your best sellers.

    ```
    Feature these subscription products:

    Product 1: [Name]
    Image: [URL]
    Regular price: $[X]
    Subscription price: $[X] (save [X]%)

    Product 2: [Name]
    Image: [URL]
    Regular price: $[X]
    Subscription price: $[X] (save [X]%)

    Product 3: [Name]
    Image: [URL]
    Regular price: $[X]
    Subscription price: $[X] (save [X]%)

    Show the actual dollar savings, not just the percentage. "$8 per order" is more tangible than "15% off."
    ```

    <Warning>
      Don't feature products with unpredictable usage rates (like occasional-use items) as subscription heroes. Customers won't subscribe to products they can't confidently predict running out of.
    </Warning>
  </Accordion>

  <Accordion title="Build your FAQ section">
    Your FAQ should address the top subscription objections: commitment, cancellation difficulty, and flexibility.

    ```
    Add these essential FAQs:

    Q: Can I cancel anytime?
    A: [Yes, cancel anytime with no fees or penalties. Explain how: through account portal, customer service, etc.]

    Q: How do I skip or pause a delivery?
    A: [Explain the process and deadline for changes]

    Q: When will I be charged?
    A: [Explain billing timing and how customers can view upcoming charges]

    Q: Can I change my products or delivery frequency?
    A: [Explain flexibility to swap products or adjust timing]

    Q: What if I'm not satisfied?
    A: [Address returns, refunds, or satisfaction guarantee]

    Q: Is there a minimum commitment?
    A: [Clarify there's no minimum or obligation]
    ```

    <Tip>
      Add a FAQ about shipping costs. If subscribers get free shipping, make that explicit. If shipping is charged, state the cost upfront to avoid cart abandonment.
    </Tip>
  </Accordion>

  <Accordion title="Add social proof">
    Subscription conversion improves 15-30% when you include proof that others subscribe successfully.

    ```
    Add one of these social proof elements:

    Option 1 - Subscriber count:
    "Join [X,000+] subscribers who never run out"

    Option 2 - Customer testimonial:
    Quote: "[Actual customer quote about subscription convenience or value]"
    Attribution: [Customer name or first name + initial]

    Option 3 - Rating:
    "[4.8] star rating from [X] subscription customers"

    Use real numbers. Round to the nearest hundred or thousand for credibility without overpromising.
    ```
  </Accordion>
</AccordionGroup>

## Step 3: Personalize the Design

The output will use your brand styles automatically. But subscription pages need to balance urgency with trust.

<Tabs>
  <Tab title="From a URL">
    Reference any URL to pull design inspiration—whether it's a competitor's subscription page or a successful brand in your space.

    **How to:**

    ```
    Can you replicate the layout from [input URL] and apply it to my subscription page?

    I like how it uses [benefit icons, stepped process visualization, prominent FAQ section, testimonial placement].
    ```

    This works well when you've found a subscription page structure that clearly communicates value and reduces friction.
  </Tab>

  <Tab title="From a Screenshot">
    Drop an image to show the exact visual hierarchy you want.

    **How to:**

    ```
    Can you apply the layout from this image to my subscription page?

    I want the same [hero treatment, benefit cards, how-it-works visualization, FAQ accordion style].
    ```

    This is useful when you need to match the visual treatment to your brand's existing page templates or style.
  </Tab>

  <Tab title="From a Prompt">
    Use natural language to describe the visual hierarchy. Be specific about how you want to balance benefit communication with conversion elements.

    **Example:**

    ```
    I want this subscription page to feel [trustworthy and low-commitment, not pushy].

    Use [clean layouts with plenty of white space]. The benefits section should use [icons and short headlines] to make scanning easy.

    The hero should emphasize [the discount and convenience], not create false urgency. The FAQ section should be [prominently placed and easy to expand].

    Use [soft colors for CTAs] instead of aggressive high-contrast buttons. The overall tone should be [helpful and transparent].
    ```

    <Tip>
      Subscription pages perform better when they feel like you're helping customers make a smart decision, not pressuring them into commitment. Use trust-building design language: clear FAQs, prominent cancellation policies, and transparent pricing.
    </Tip>
  </Tab>
</Tabs>

## Step 4: Set Up Your Links

Use [edit mode](https://docs.replo.app/features/edit-mode) to select product cards and CTAs. Once selected:

```
This product card should link to [Product URL with subscription parameter or subscription variant].
```

Or to update all product links at once:

```
Update all product cards to link to their subscription pages:
[Product 1] → [URL 1]
[Product 2] → [URL 2]
[Product 3] → [URL 3]
```

For main CTAs:

```
This "Start Subscription" button should link to [your main subscription product page or shop page with subscription filter].
```

## Step 5: Publish

Once your subscription page is ready, click **Publish** in the top-right corner:

1. **Publish to your Replo domain** — every workspace gets a default domain (e.g. `yourshop.reploshops.com`). Use this for testing or to share preview links with your team.
2. **Add a custom domain** — connect your own domain to publish under your brand's URL (e.g. `yourbrand.com/pages/subscribe` or `yourbrand.com/subscription`).

[*Read more about Publishing and Custom Domains*](https://docs.replo.app/features/publish)*.*

Click **Publish** again to make your subscription page live.

## Subscription Page Performance Tips

Subscription pages drive long-term profitability by increasing LTV. Optimize for subscriber acquisition, not just immediate conversion:

* **Lead with the economics** — show the actual dollar savings, not just percentage. "Save \$12 per order" converts better than "Save 15%."
* **Make cancellation obvious** — counterintuitively, emphasizing easy cancellation increases subscription conversion. Customers fear being locked in. Remove that fear.
* **Test discount depth** — most brands see optimal conversion between 15-25% off. Higher discounts increase subscriber acquisition but reduce first-order margin. Track CAC/LTV ratio at different discount levels.
* **Add subscriber LTV tracking** — subscribers typically have 3-5x higher LTV than one-time buyers. Track cohort-level subscriber retention and LTV separately from general customer metrics.
* **Run subscription-specific ads** — traffic from ads promising subscription discounts converts 40-60% better than sending subscription traffic to regular product pages. Dedicated subscription landing pages are essential.
* **Use email to convert one-time buyers** — customers who buy once are 3-4x more likely to subscribe than cold traffic. Email them post-purchase highlighting subscription benefits.
* **Optimize for mobile** — 60-70% of subscription signups happen on mobile. Ensure your benefits, process, and FAQ are scannable on small screens.
* **Track by acquisition channel** — paid social subscribers often have lower retention than email-acquired subscribers. Segment your subscriber analysis by source to understand true unit economics.

<Warning>
  Don't hide your regular product pages to force subscriptions. Customers who want to try before subscribing will bounce. Let them buy once first—then convert them to subscription through email and retargeting.
</Warning>

## Subscription Conversion Benchmarks

Track these metrics to optimize your subscription page:

**Subscription Rate** — percentage of product page visitors who choose subscription over one-time purchase. Typical range: 8-25% for consumables, 3-8% for durables.

**Subscriber CAC** — acquisition cost for customers who convert to subscription. Usually 20-40% higher than one-time CAC, but justified by LTV lift.

**First-Order Subscription CVR** — percentage of landing page visitors who subscribe on first visit. Typical range: 2-6%.

**Subscriber LTV** — total revenue from subscription customers. Should be 3-5x higher than one-time buyer LTV.

**Subscription Retention** — percentage of subscribers still active after 3, 6, and 12 months. Target 60%+ at 3 months, 40%+ at 6 months, 25%+ at 12 months.

**Churn Rate** — percentage of subscribers who cancel each month. Target under 10% monthly churn for consumables.

<Tip>
  Your subscription page is not just for new customer acquisition. Use it in retention campaigns to convert existing one-time buyers. They already trust your product—the page just needs to make subscription benefits obvious.
</Tip>
