Skip to content

Refactor/replace hardcoded urls configurable via env vars#374

Open
sekhar08 wants to merge 2 commits intodatabuddy-analytics:mainfrom
sekhar08:refactor/replace-hardcoded-urls-configurable-via-env-vars
Open

Refactor/replace hardcoded urls configurable via env vars#374
sekhar08 wants to merge 2 commits intodatabuddy-analytics:mainfrom
sekhar08:refactor/replace-hardcoded-urls-configurable-via-env-vars

Conversation

@sekhar08
Copy link
Copy Markdown
Contributor

@sekhar08 sekhar08 commented Apr 2, 2026

Description

Makes all hardcoded production URLs configurable via environment variables to support self-hosting deployments.

Previously, several services contained hardcoded *.databuddy.cc URLs with no way to override them. Self-hosters would silently have tracking snippets, link redirects, and CORS origins all pointing back to the hosted version. All new env vars default to the current production values, so existing deployments are unaffected.

Changes by service:

  • linksAPP_URL controls expired/not-found/proxy redirect destinations; LINKS_ROOT_REDIRECT_URL controls root / redirect; GEOIP_DB_URL controls GeoLite2 MMDB
    source
  • basket/apiGEOIP_DB_URL also applied to apps/cron/geo.ts
  • dashboardNEXT_PUBLIC_BASKET_URL controls where tracking events are sent; NEXT_PUBLIC_TRACKER_URL controls the script URL shown in install snippets
  • apiDASHBOARD_URL is added to CORS allowed origins for self-hosted dashboard instances

New files:

  • SELF_HOSTING.md — full self-hosting guide with env var reference table, example .env, and a complete docker-compose.yml
  • .env.example updated with all 6 new variables

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have made corresponding changes to the documentation (SELF_HOSTING.md, .env.example, apps/dashboard/.env.example)
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

▎ Note on the tests checkbox: no new tests added — these are env var fallbacks with defaults matching the previous hardcoded values, so no behavior changes to test. You can
leave it unchecked or add a note to that effect

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 2, 2026

@sekhar08 is attempting to deploy a commit to the Databuddy OSS Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f6e6d0f0-6ac9-4840-817d-57ee84f853d2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 2, 2026

Greptile Summary

This PR replaces hardcoded *.databuddy.cc URLs across multiple services with configurable environment variables, enabling self-hosted deployments to point to their own infrastructure. It also adds a comprehensive SELF_HOSTING.md guide with an env var reference table, example .env, and a full docker-compose.yml.

The approach is sound and the defaults preserve existing behavior for hosted deployments. However, there are a few issues worth addressing before merge:

  • NEXT_PUBLIC_* env vars are build-time in Next.js — the docker-compose example in SELF_HOSTING.md only passes NEXT_PUBLIC_BASKET_URL and NEXT_PUBLIC_TRACKER_URL as runtime environment: entries, which have no effect on an already-built Next.js bundle. Build args (build.args:) and matching ARG declarations in the Dockerfile are required for these to work.
  • _apiUrl in code-generators.ts is computed but never emitted in NPM snippets — self-hosters whose users copy the NPM/React install snippet will get a <Databuddy> component without apiUrl, silently defaulting to basket.databuddy.cc.
  • DASHBOARD_URL trailing slash — a DASHBOARD_URL value with a trailing slash will silently fail CORS matching; the value should be normalised before use.

Confidence Score: 3/5

Safe to merge for the hosted product (all defaults are unchanged), but the self-hosting guide has a build-time/runtime confusion for Next.js NEXT_PUBLIC_ vars that will frustrate self-hosters.

The code changes themselves are low-risk — simple env var fallbacks with production defaults mean existing deployments are unaffected. The main concern is that the primary deliverable (the self-hosting guide) contains a docker-compose example that won't work as written for NEXT_PUBLIC_* vars, and the NPM snippet generators don't surface the configurable basket URL, which is a silent gap for self-hosters.

SELF_HOSTING.md (docker-compose build args for NEXT_PUBLIC_* vars), apps/dashboard/app/(main)/websites/[id]/_components/utils/code-generators.ts (_apiUrl unused in NPM snippets), apps/api/src/index.ts (DASHBOARD_URL trailing slash normalisation)

Important Files Changed

Filename Overview
SELF_HOSTING.md Comprehensive self-hosting guide, but the docker-compose example passes NEXT_PUBLIC_* vars only as runtime environment: entries — these are build-time inlined in Next.js and will have no effect without build.args: + Dockerfile ARG declarations.
apps/api/src/index.ts Adds DASHBOARD_URL to the CORS allowed origins list — correct approach, but a trailing slash in the env value will silently cause CORS failures; normalization is missing.
apps/dashboard/app/(main)/websites/[id]/_components/utils/code-generators.ts Replaces hardcoded URLs with NEXT_PUBLIC_BASKET_URL / NEXT_PUBLIC_TRACKER_URL env vars in the script-tag generator, but _apiUrl remains unused and the NPM snippet generators don't emit apiUrl for self-hosted basket endpoints.
apps/dashboard/app/layout.tsx Replaces hardcoded basket URL with NEXT_PUBLIC_BASKET_URL env fallback — correct change, but effectiveness depends on var being set at Next.js build time, not just at container runtime.
apps/links/src/routes/redirect.ts Replaces three hardcoded app.databuddy.cc URLs with a single configurable APP_URL constant — clean refactor; env var is evaluated at module load time which is safe but worth noting.
apps/links/src/utils/geo.ts Replaces hardcoded GeoLite2 CDN URL with GEOIP_DB_URL env fallback — straightforward and correct.
apps/cron/geo.ts Same GEOIP_DB_URL env var applied to cron geo helper — consistent with the links service change, no issues.
apps/links/src/index.ts Root / redirect now reads from LINKS_ROOT_REDIRECT_URL env var with a safe fallback — clean change.
.env.example Documents all six new env vars with production defaults; DASHBOARD_URL correctly defaults to empty to avoid accidental CORS grants.
apps/dashboard/.env.example Adds NEXT_PUBLIC_BASKET_URL and NEXT_PUBLIC_TRACKER_URL with production defaults — good documentation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    ENV["Environment Variables"]

    ENV -->|NEXT_PUBLIC_BASKET_URL| DASH_LAYOUT["dashboard/app/layout.tsx\n(self-tracking)"]
    ENV -->|NEXT_PUBLIC_BASKET_URL\nNEXT_PUBLIC_TRACKER_URL| CODE_GEN["code-generators.ts\n(install snippet)"]
    ENV -->|DASHBOARD_URL| API_CORS["apps/api/src/index.ts\nCORS origins"]
    ENV -->|APP_URL| REDIRECT["apps/links/src/routes/redirect.ts\nExpired / Not-found / Proxy URLs"]
    ENV -->|LINKS_ROOT_REDIRECT_URL| ROOT["apps/links/src/index.ts\nRoot / redirect"]
    ENV -->|GEOIP_DB_URL| GEO_LINKS["apps/links/src/utils/geo.ts\nGeoIP DB fetch"]
    ENV -->|GEOIP_DB_URL| GEO_CRON["apps/cron/geo.ts\nGeoIP DB fetch"]

    CODE_GEN -->|script src=| TRACKER_SCRIPT["Tracking JS bundle\n(end user browser)"]
    CODE_GEN -->|data-api-url=| BASKET_SVC["Basket event service\n(end user browser)"]
    DASH_LAYOUT -->|apiUrl=| BASKET_SVC

    style ENV fill:#f0f4ff,stroke:#6366f1
    style CODE_GEN fill:#fef3c7,stroke:#f59e0b
    style DASH_LAYOUT fill:#fef3c7,stroke:#f59e0b
Loading

Reviews (1): Last reviewed commit: "feat: add self-hosting environment varia..." | Re-trigger Greptile

Comment on lines +253 to +260
NODE_ENV: production
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
clickhouse:
condition: service_healthy
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 NEXT_PUBLIC_* vars are baked in at Next.js build time, not runtime

In Next.js, any variable prefixed with NEXT_PUBLIC_ is statically inlined into the JavaScript bundle at build time. Setting them under environment: in docker-compose.yml only makes them available to the running container — they will not be picked up by an already-built Next.js app.

As a result, a self-hoster who follows this guide will build the dashboard image, run it with NEXT_PUBLIC_BASKET_URL=https://basket.example.com, and discover that the tracking snippets and the layout tracker still point to basket.databuddy.cc because those values were substituted when the bundle was compiled.

The fix is to pass them as Docker ARGs in the Dockerfile and expose them under build.args: in the Compose file so they are available during next build:

dashboard:
  build:
    context: .
    dockerfile: apps/dashboard/Dockerfile
    args:
      NEXT_PUBLIC_BASKET_URL: ${NEXT_PUBLIC_BASKET_URL:-https://basket.databuddy.cc}
      NEXT_PUBLIC_TRACKER_URL: ${NEXT_PUBLIC_TRACKER_URL:-https://cdn.databuddy.cc/databuddy.js}
  environment:
    NEXT_PUBLIC_BASKET_URL: ${NEXT_PUBLIC_BASKET_URL:-https://basket.databuddy.cc}
    NEXT_PUBLIC_TRACKER_URL: ${NEXT_PUBLIC_TRACKER_URL:-https://cdn.databuddy.cc/databuddy.js}
    ...

The dashboard Dockerfile must also declare matching ARG instructions before the RUN bun run build step. Without this change, NEXT_PUBLIC_BASKET_URL and NEXT_PUBLIC_TRACKER_URL will silently have no effect at runtime.

...(process.env.NODE_ENV === "development"
? ["http://localhost:3000"]
: []),
...(process.env.DASHBOARD_URL ? [process.env.DASHBOARD_URL] : []),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 DASHBOARD_URL with trailing slash silently breaks CORS

CORS origin matching is exact — the browser sends the origin as https://app.example.com (no trailing slash), but if a user configures DASHBOARD_URL=https://app.example.com/ (with trailing slash), the string comparison will never match and all cross-origin requests from that dashboard will be blocked with no obvious error message.

Consider stripping a trailing slash when consuming the variable:

Suggested change
...(process.env.DASHBOARD_URL ? [process.env.DASHBOARD_URL] : []),
...(process.env.DASHBOARD_URL ? [process.env.DASHBOARD_URL.replace(/\/$/, "")] : []),

Comment on lines 15 to +17
const _apiUrl = isLocalhost
? "http://localhost:4000"
: "https://basket.databuddy.cc";
: (process.env.NEXT_PUBLIC_BASKET_URL || "https://basket.databuddy.cc");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 _apiUrl is computed but never embedded in NPM snippets

_apiUrl is resolved with the new NEXT_PUBLIC_BASKET_URL fallback in generateScriptTag, but its value is never actually used in the returned <script> tag (note the leading _). More importantly, generateNpmCode and generateNpmComponentCode — the functions that produce the NPM/React install snippets — compute no equivalent value, so those snippets always omit the apiUrl prop entirely. A self-hoster whose users copy the NPM snippet will get a <Databuddy> component with no apiUrl, and the SDK will default to basket.databuddy.cc rather than the self-hoster's basket endpoint.

Consider either injecting apiUrl={...} into the NPM snippet when the basket URL differs from the production default, or at minimum documenting that NPM users must add the apiUrl prop manually.

Comment on lines +22 to +25
const APP_URL = process.env.APP_URL || "https://app.databuddy.cc";
const EXPIRED_URL = `${APP_URL}/dby/expired`;
const NOT_FOUND_URL = `${APP_URL}/dby/not-found`;
const PROXY_URL = `${APP_URL}/dby/l`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Module-level APP_URL evaluation: env var must be set before first import

APP_URL (and the derived EXPIRED_URL, NOT_FOUND_URL, PROXY_URL) are evaluated once when the module is first imported. This means the APP_URL env var must be present in the process environment before this module is loaded. Any late-binding pattern (e.g., dotenv loaded after imports) will cause the constant to lock in the empty/default value permanently for the lifetime of the process.

This is fine as long as the service's entrypoint loads .env before importing application modules, which is the common pattern. Worth a brief note in the self-hosting docs to ensure APP_URL is set in the process environment rather than relying on a deferred .env load.

@sekhar08
Copy link
Copy Markdown
Contributor Author

sekhar08 commented Apr 2, 2026

Hey @izadoesdev, I've added a SELF_HOSTING.md‎ in this PR, should i keep it or remove it?

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.

1 participant