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

# Build a Subscription Landing Page

> Build a subscription landing page that turns one-time buyers into subscribers.

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 API_BASE = "https://api.replo.app";
  const APP_SIGNUP_URL = "https://dashboard.replo.app/auth/signup";
  const showToggle = desktopImageSource && mobileImageSource;
  const currentImage = showToggle && isMobile ? mobileImageSource : desktopImageSource;
  const handleMouseDown = (event, container) => {
    setIsDragging(true);
    setDragStart({
      x: event.clientX,
      y: event.clientY
    });
    setScrollStart({
      x: container.scrollLeft,
      y: container.scrollTop
    });
    setLastPos({
      x: event.clientX,
      y: event.clientY
    });
    setLastTime(Date.now());
    setVelocity({
      x: 0,
      y: 0
    });
    setScrollContainer(container);
    event.preventDefault();
  };
  const handleMouseMove = (event, container) => {
    if (!isDragging) return;
    const now = Date.now();
    const elapsedMs = now - lastTime;
    if (elapsedMs > 0) {
      const deltaX = event.clientX - dragStart.x;
      const deltaY = event.clientY - dragStart.y;
      const velocityX = (event.clientX - lastPos.x) / elapsedMs;
      const velocityY = (event.clientY - lastPos.y) / elapsedMs;
      container.scrollLeft = scrollStart.x - deltaX;
      container.scrollTop = scrollStart.y - deltaY;
      setVelocity({
        x: velocityX,
        y: velocityY
      });
      setLastPos({
        x: event.clientX,
        y: event.clientY
      });
      setLastTime(now);
    }
    event.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 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(`${API_BASE}/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 (caughtError) {
      console.error("Failed to generate prompt:", caughtError);
      const message = caughtError instanceof Error ? caughtError.message : null;
      setError(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={event => {
    handleMouseDown(event, event.currentTarget);
  }} onMouseMove={event => {
    handleMouseMove(event, event.currentTarget);
  }}>
                <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={event => {
    handleMouseDown(event, event.currentTarget);
  }} onMouseMove={event => {
    handleMouseMove(event, event.currentTarget);
  }}>
                <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={event => {
    if (!isLoading) {
      event.currentTarget.style.backgroundColor = "rgb(59, 94, 246)";
    }
  }} onMouseLeave={event => {
    if (!isLoading) {
      event.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 [select mode](/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.replosites.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*](/publishing)*.*

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>
