Skip to content

Fix localhost redirect URLs on Railway/reverse proxy deployments - #25

Merged
cavewebs merged 5 commits into
mainfrom
cursor/fix-localhost-redirects-c2c9
Sep 15, 2026
Merged

cavewebs merged 5 commits into
mainfrom
cursor/fix-localhost-redirects-c2c9

Conversation

@cavewebs

@cavewebs cavewebs commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Problem

On https://demo.dashcommerce.dev (Railway + Neon deployment), redirect URLs were using http://localhost:8080 instead of the public domain:

  • Stripe checkout success redirects: http://localhost:8080/thank-you/<id>?session_id=...
  • Admin vendor invite links: http://localhost:8080/vendor/...
  • Billing portal return URLs: http://localhost:8080/account

Root Causes (Two Issues)

1. Request URL Behind Reverse Proxy

The code was extracting the origin from routeCtx.request.url, which contains the internal routing URL when behind a reverse proxy. Railway (and similar platforms) route traffic through an internal network where the app listens on localhost:8080, while the public domain is handled by the proxy.

2. Stale Database Option

The live Neon database had:

emdash:site_url = "http://localhost:8080"  ← Stale from dev/setup

While the environment variable was correct (SITE_URL=https://demo.dashcommerce.dev), this stale database option may have been read by EmDash core or could interfere with site URL resolution.

Solution

Code Hardening

Created getPublicSiteUrl() helper in util/site-url.ts that:

  • Uses ctx.site.url (from SITE_URL env var) instead of request.url.origin
  • Validates the URL is configured (throws if missing)
  • Logs warnings if localhost is detected in production
  • Strips trailing slashes for clean URL construction
  • Provides isProductionSiteUrl() utility for conditional logic

Files changed:

  • packages/core/src/util/site-url.ts - New helper module
  • packages/core/src/routes/checkout.ts - Stripe checkout success/cancel URLs
  • packages/core/src/routes/subscriptions-public.ts - Billing portal return URL
  • packages/core/src/routes/customer-portal.ts - Customer portal return URL

Database Cleanup (Recommended)

Parent is handling cleanup of the stale database option:

UPDATE options 
SET value = '"https://demo.dashcommerce.dev"'
WHERE key = 'emdash:site_url';

The code fix ensures correct behavior even if this option remains stale, but cleaning it up prevents debugging confusion.

Required Configuration

Deployments must set the SITE_URL environment variable to the public domain:

SITE_URL=https://demo.dashcommerce.dev

Railway already has this configured for the demo. No application code changes or restarts needed beyond deploying this PR.

Testing

To verify on the live demo after deployment:

  1. Complete a Stripe test checkout
  2. Verify the success redirect goes to https://demo.dashcommerce.dev/thank-you/...
  3. Create a vendor invite in admin
  4. Verify the invite link uses https://demo.dashcommerce.dev/vendor/...
  5. Check logs for any "Site URL is localhost" warnings (should be none after DB cleanup)

Documentation

See LOCALHOST_REDIRECT_FIX.md for:

  • Complete technical analysis
  • SQL queries to check/fix database options
  • Database option vs environment variable hierarchy
  • Verification procedures
  • Platform-specific configuration guidance

Design Decision

The helper warns but allows localhost URLs to support development and testing environments. In production with SITE_URL set correctly, no warnings will appear. This design:

  • Fails fast if SITE_URL is completely missing
  • Aids debugging when misconfigured
  • Doesn't break local development
  • Centralizes URL validation logic
Open in Web Open in Cursor 

cursoragent and others added 5 commits September 15, 2026 01:08
Fixes localhost redirect bug on Railway and other reverse proxy deployments.

When deployed behind a reverse proxy (e.g. Railway, nginx), routeCtx.request.url
contains the internal routing URL (http://localhost:8080) instead of the public
domain. This causes Stripe checkout redirects and billing portal return URLs to
point to localhost instead of the production domain.

Changes:
- Use ctx.site.url (populated from SITE_URL env var) for all redirect URLs
- Affects: checkout success/cancel URLs, billing portal return URLs
- Strip trailing slashes from ctx.site.url for clean URL construction

Requires SITE_URL environment variable to be set in production deployments.

Co-authored-by: Timchosen Uzua <timchosen@gmail.com>
…irect bug

Co-authored-by: Timchosen Uzua <timchosen@gmail.com>
Hardens against stale database options (emdash:site_url) that may contain
localhost values from development. Creates a centralized helper that:

- Validates ctx.site.url is configured
- Strips trailing slashes for clean URL construction
- Logs warnings if localhost is detected in production
- Provides isProductionSiteUrl() utility for conditional logic

This ensures Stripe redirect URLs, billing portal return URLs, and admin
invite links always use the public domain even if database options are stale.

Changes:
- Add util/site-url.ts with getPublicSiteUrl() and isProductionSiteUrl()
- Update checkout.ts, subscriptions-public.ts, customer-portal.ts to use helper
- Helper throws if ctx.site.url is missing (config error)
- Helper warns but allows localhost (useful for dev/test)

Co-authored-by: Timchosen Uzua <timchosen@gmail.com>
Clarifies the two root causes:
1. Request URL extraction behind reverse proxy
2. Stale emdash:site_url database option

Adds:
- SQL queries to check and fix database options
- Database option vs environment variable hierarchy explanation
- getPublicSiteUrl() helper behavior documentation
- Guidance on when to update database options

Documents that parent is handling database cleanup for live demo.

Co-authored-by: Timchosen Uzua <timchosen@gmail.com>
Quick reference guide for deploying DashCommerce on Railway, Heroku, GCP, AWS, etc.

Covers:
- Required environment variables (SITE_URL, DATABASE_URL, Stripe keys)
- Post-deployment database verification
- Common issues and fixes (localhost redirects, domain changes)
- Platform-specific notes (Railway, Heroku, Cloudflare, Docker/K8s)
- Verification commands and monitoring tips

Co-authored-by: Timchosen Uzua <timchosen@gmail.com>
@cavewebs
cavewebs marked this pull request as ready for review September 15, 2026 01:16
@cavewebs
cavewebs merged commit d6d1e9a into main Sep 15, 2026
2 checks passed
@cavewebs
cavewebs deleted the cursor/fix-localhost-redirects-c2c9 branch September 15, 2026 01:16
cavewebs added a commit that referenced this pull request Sep 15, 2026
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary

Adds a patch changeset for PR #25 which fixed localhost redirect URLs on
Railway/reverse proxy deployments.

PR #25 was merged without a changeset, so npm will not publish the fix.
This PR adds the required changeset to trigger a version bump and npm
publish.

## Changes

- ✅ Add patch changeset for `@dashcommerce/core` describing the
Railway/reverse-proxy URL fix
- ✅ Move `LOCALHOST_REDIRECT_FIX.md` and `OPERATOR_CHECKLIST.md` from
repo root to `docs/` directory

## Changeset Details

**Package:** `@dashcommerce/core`  
**Version bump:** patch  
**Summary:** Railway/reverse-proxy: use configured public site URL for
Stripe success/cancel and portal return URLs instead of request.origin
which can be localhost:PORT

## Context

PR #25 implemented `getPublicSiteUrl()` helper to fix Stripe redirect
URLs and admin invite links that were incorrectly pointing to
`http://localhost:8080` instead of the public domain when deployed
behind a reverse proxy (Railway, nginx, etc.).

The fix ensures URLs are constructed using `ctx.site.url` (from
`SITE_URL` env var) instead of `request.origin`.

## Related

- Fixes the missing changeset from #25
- Organizes operational documentation in `docs/` directory
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-6c01642d-921e-5817-b6a6-6a71e7045e4e?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-6c01642d-921e-5817-b6a6-6a71e7045e4e&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants