Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .dev.vars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Local development secrets for Wrangler (`wrangler dev` / OpenNext preview).
# Wrangler reads this file automatically when present; do NOT commit a
# real .dev.vars file (it is gitignored).
#
# Copy to .dev.vars and fill in real values. Never commit .dev.vars.

NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
NEXT_PUBLIC_SITE_URL=http://localhost:3000

# Service-role key and cron secret are kept in plain var slots here so
# local `wrangler dev` / `opennextjs-cloudflare preview` can run. For
# real environments, put SUPABASE_SERVICE_ROLE_KEY and CRON_SECRET behind
# `wrangler secret put <NAME>` or the Cloudflare dashboard so they are
# encrypted at rest.
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
CRON_SECRET=your-cron-secret
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
# opennext / cloudflare build output
/.open-next/

# wrangler local state (preview deploys, dev mode scratch)
/.wrangler/

# wrangler dev / opennextjs-cloudflare preview local secrets (real values).
# Use .dev.vars.example as a template; never commit .dev.vars.
.dev.vars

# production
/build

Expand Down
134 changes: 78 additions & 56 deletions OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,22 @@ The platform runs on a weekly cycle: **Saturday 00:00 → Friday 14:29** (Helsin
- The community votes and leaves feedback
- Every **Friday 14:30 – 15:30 Helsinki time**, top projects demo live on Google Meet

### Automated Cron (Cloudflare Cron Triggers)
### Automated Cron

The platform runs on **Cloudflare Pages** (via OpenNext). Cron jobs are configured via Cloudflare Cron Triggers in `wrangler.toml`:
The cron path (`GET /api/cron/demo-day`) is a **Next.js Route Handler**. It is triggered by authenticated HTTP requests — there is no built-in scheduled event wired up today.

```toml
[triggers]
crons = ["30 11 * * 5"] # Every Friday 11:30 UTC (14:30 Helsinki)
```

The trigger calls the same handler that the old `vercel.json` cron did:
Recommended ways to invoke it on a schedule (in order of current practicality):

- Endpoint: `GET /api/cron/demo-day`
- Authenticated via `CRON_SECRET` env var (Bearer token)
- What it does:
1. Queries the top 3 products by votes for the current week
2. Inserts rows into `demo_day_winners`
3. Marks the `demo_days` row as `completed`
1. **Admin panel "Take snapshot now" button** — calls the route handler from the browser. No schedule, but it's the simplest "snap it now" path.
2. **Manual API call** with the cron bearer token:
```bash
curl -H "Authorization: Bearer YOUR_CRON_SECRET" \
https://productbuilders.app/api/cron/demo-day
```
3. **External scheduler** (any HTTP cron service: cron-job.org, EasyCron, GitHub Actions cron, etc.) — point it at `https://productbuilders.app/api/cron/demo-day` with the bearer header.
4. **Supabase SQL** — see "Manual Trigger" below.

> Legacy: the repo still contains `vercel.json` from an earlier Vercel deployment. It is harmless on Cloudflare (Cloudflare ignores it) and is kept as a historical reference. Delete it once you confirm Cloudflare Cron Triggers are working.
> **Why not Wrangler Cron Triggers?** `@opennextjs/cloudflare` 1.20.1 does **not** expose a `scheduled` handler that bridges to a Next.js Route Handler. The OpenNext Worker entry only implements `fetch`. A future adapter release (or a customization) can wire this up; see `docs/CLOUDFLARE_ADAPTER.md` for status.

### Manual Trigger

Expand All @@ -42,9 +39,8 @@ curl -H "Authorization: Bearer YOUR_CRON_SECRET" \
https://productbuilders.app/api/cron/demo-day
```

**Option C: Cloudflare dashboard**
1. Open the Pages project → **Settings → Functions → Cron Triggers**.
2. Click **Trigger** next to the demo-day entry.
**Option C: External scheduler**
Point any HTTP cron service at `https://productbuilders.app/api/cron/demo-day` with header `Authorization: Bearer <CRON_SECRET>`. Recommended schedule: `30 11 * * 5` UTC (Friday 11:30 UTC = 14:30 Helsinki winter; adjust for DST).

**Option D: Supabase SQL**
```sql
Expand All @@ -65,70 +61,96 @@ LIMIT 3;

## Cloudflare setup

The platform runs on **Cloudflare Workers** via OpenNext (`@opennextjs/cloudflare`). It is **not** a Cloudflare Pages project — OpenNext for Cloudflare compiles the Next.js app into a single Worker entry (`main: .open-next/worker.js`) and uses an `assets` binding for static files.

### 1. OpenNext adapter

```bash
npm install --save-dev @opennextjs/cloudflare
npm install --save-dev @opennextjs/cloudflare wrangler
```

### 2. `wrangler.toml` template

```toml
name = "productbuilders-app"
compatibility_date = "2025-01-01" # Owner must confirm latest stable date
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".open-next/dist"
### 2. `wrangler.jsonc` template

The repo ships `wrangler.jsonc` at the project root. Fill in the values before deploying:

```jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"main": ".open-next/worker.js",
"name": "productbuilders-app",
"compatibility_date": "2025-03-01",
"compatibility_flags": ["nodejs_compat"],
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS"
},
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "productbuilders-app"
}
],
"vars": {
"NEXT_PUBLIC_SUPABASE_URL": "",
"NEXT_PUBLIC_SUPABASE_ANON_KEY": "",
"NEXT_PUBLIC_SITE_URL": "https://productbuilders.app"
}
}
```

[vars]
# Public vars only. Secrets go under [[secrets]] or in the dashboard.
# Owner must confirm: NEXT_PUBLIC_SUPABASE_URL
# Owner must confirm: NEXT_PUBLIC_SUPABASE_ANON_KEY
- `name` MUST match the Workers project name in the Cloudflare dashboard.
- `compatibility_date` should be set to a recent stable date. Update it when bumping the worker runtime.
- `vars` here are build-time *public* values. Anything sensitive (`SUPABASE_SERVICE_ROLE_KEY`, `CRON_SECRET`) MUST be set via `wrangler secret put <NAME>` or in the Cloudflare dashboard.

# Secrets (set via `wrangler secret put <NAME>` or in the Cloudflare dashboard):
# SUPABASE_SERVICE_ROLE_KEY
# CRON_SECRET
### 3. `open-next.config.ts`

[triggers]
crons = ["30 11 * * 5"] # Friday 11:30 UTC = 14:30 Helsinki (winter). Adjust for DST if needed.
```
The repo ships a minimal `open-next.config.ts` at the root. It calls `defineCloudflareConfig({})` with no overrides — no R2-backed incremental cache, no queue bindings, no image optimizer. Add overrides later if those become hard requirements.

### 3. Build & preview
### 4. Build & preview

```bash
npm run build # standard next build (good for local sanity)
npm run preview # OpenNext build + local preview server
npm run deploy # OpenNext build + push to Cloudflare Pages
npm run build # standard next build (good for local sanity and CI)
npm run cf:build # OpenNext build → .open-next/worker.js
npm run preview # OpenNext build + local preview server (wrangler)
npm run deploy # OpenNext build + push to Cloudflare Workers
```

If you add those scripts to `package.json`:
### 5. Local secrets

```json
"scripts": {
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
}
```
Copy `.dev.vars.example` to `.dev.vars` for local `wrangler dev` / `npm run preview`. `.dev.vars` is gitignored.

### 4. Environment variables
### 6. Environment variables (production)

Set in **Cloudflare dashboard → Pages → productbuilders-app → Settings → Environment variables** (per environment: Production and Preview):
Set in **Cloudflare dashboard → Workers → productbuilders-app → Settings → Variables** (per environment: Production and Preview):

| Variable | Visibility | Notes |
|---|---|---|
| `NEXT_PUBLIC_SUPABASE_URL` | Public | injected at build time |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Public | injected at build time |
| `NEXT_PUBLIC_SUPABASE_URL` | Public | injected at build time (also set as `vars` in wrangler.jsonc) |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Public | injected at build time (also set as `vars` in wrangler.jsonc) |
| `NEXT_PUBLIC_SITE_URL` | Public | trusted redirect origin for `/auth/callback` |
| `SUPABASE_SERVICE_ROLE_KEY` | Secret | only the cron handler reads it |
| `CRON_SECRET` | Secret | bearer token for `/api/cron/demo-day` |

Never commit any of these.

### 5. Verifying a deployment
### 7. Verifying a deployment

- Open the Cloudflare Pages deployment URL.
- `View logs` → real-time Function logs (auth callback, cron handler).
- Open the Cloudflare Workers deployment URL.
- `View logs` → real-time Worker logs (auth callback, cron handler).
- `View build logs` → OpenNext build output.

### 8. Deploy blocker (Next 16 `proxy.ts`)

`npm run cf:build` currently fails with:

```
ERROR Node.js middleware is not currently supported. Consider switching to Edge Middleware.
```

This is an upstream OpenNext issue (opennextjs-cloudflare#1277) — the adapter does not yet recognize Next.js 16's `proxy.ts` convention (which this project uses). The fix (PR #1280) is open and not yet merged. Until it lands, `npm run cf:build` will not produce a Worker and `npm run deploy` will not succeed.

Workaround today: use `wrangler dev` with `next.config.ts`'s `initOpenNextCloudflareForDev()` to verify the adapter shape locally — but do not attempt a real deploy until upstream lands.

## Admin Access

To make a user an admin:
Expand All @@ -151,4 +173,4 @@ The admin panel at `/admin` allows:

Product images are stored in the `product-images` Supabase Storage bucket.
- Max file size: 2MB (enforced client-side)
- Path format: `products/{user_id}/{timestamp}.{ext}`
- Path format: `products/{user_id}/{timestamp}.{ext}`
41 changes: 20 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,48 +102,47 @@ After creating your first account, grab your user UUID from the Supabase Auth da

## Deploy to Cloudflare

The deployment target is **Cloudflare Pages** (via OpenNext). The repo currently has scaffolding (`.open-next/`, `.wrangler/`) but no `wrangler.toml` yet — see `OPERATIONS.md` for the full setup checklist.
The deployment target is **Cloudflare Workers** via OpenNext (`@opennextjs/cloudflare`). It is **not** a Cloudflare Pages project — OpenNext compiles the Next.js app into a single Worker entry.

> **Deploy blocker:** `@opennextjs/cloudflare` 1.20.1 does not yet support Next.js 16's `proxy.ts` convention (which this project uses). `npm run cf:build` fails with "Node.js middleware is not currently supported. Consider switching to Edge Middleware." until the upstream fix lands (opennextjs-cloudflare#1280). Track the PR or watch the repo. `wrangler.jsonc`, `open-next.config.ts`, and the deploy scripts are already in place so the deploy command will work as soon as upstream lands.

### 1. Install the OpenNext Cloudflare adapter

Already installed (devDependencies):

```bash
npm install --save-dev @opennextjs/cloudflare
npm install --save-dev @opennextjs/cloudflare wrangler
```

This adds the build tooling required to produce a Cloudflare-compatible output from `next build`.

### 2. Add a `wrangler.toml`
### 2. `wrangler.jsonc`

A template lives in `OPERATIONS.md` ("Cloudflare setup" section) — copy it to `wrangler.toml` and fill in:
A `wrangler.jsonc` template ships at the project root. Fill in:

- `name` — your Cloudflare Pages project name
- `compatibility_date`
- `compatibility_flags` — typically `["nodejs_compat"]`
- `pages_build_output_dir` — point at the OpenNext build output
- `name` — your Cloudflare Workers project name
- `compatibility_date` — a recent stable date
- `compatibility_flags` — `["nodejs_compat"]` (already set)
- `vars` — `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, `NEXT_PUBLIC_SITE_URL`
- Secrets (`SUPABASE_SERVICE_ROLE_KEY`, `CRON_SECRET`) — set via `wrangler secret put <NAME>` or in the Cloudflare dashboard, not in this file.

### 3. Add the build script
### 3. Build & deploy

In `package.json`, add:

```json
"scripts": {
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
}
```bash
npm run cf:build # OpenNext build → .open-next/worker.js
npm run preview # OpenNext build + local preview server (wrangler)
npm run deploy # OpenNext build + push to Cloudflare Workers
```

### 4. Configure environment variables

In the Cloudflare dashboard for the Pages project, set:
In the Cloudflare dashboard for the Workers project, set:

- `NEXT_PUBLIC_SUPABASE_URL`
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
- `NEXT_PUBLIC_SITE_URL` (e.g. `https://productbuilders.app`) — **required in production** so the auth callback can build a trusted post-login redirect host
- `SUPABASE_SERVICE_ROLE_KEY` (used by `/api/cron/demo-day`)
- `CRON_SECRET` (used by `/api/cron/demo-day`)

See `OPERATIONS.md` for the exact locations and for the cron-trigger configuration that replaces `vercel.json`.
See `OPERATIONS.md` for the exact locations and for the cron-trigger guidance (HTTP-only today; no `scheduled` export wired up).

## Project structure

Expand Down
9 changes: 9 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@ const nextConfig: NextConfig = {
};

export default nextConfig;

// Initialize Cloudflare bindings emulation for local development with
// `wrangler dev` / `opennextjs-cloudflare preview`. Gated on development
// so `next build` (used for CI and local Next sanity) is unaffected.
if (process.env.NODE_ENV === "development") {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { initOpenNextCloudflareForDev } = require("@opennextjs/cloudflare");
initOpenNextCloudflareForDev();
}
16 changes: 16 additions & 0 deletions open-next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// OpenNext Cloudflare build configuration.
//
// `defineCloudflareConfig` is the entry point exported by
// `@opennextjs/cloudflare`. The default config is fine for this app —
// we do not enable R2-backed incremental cache here because that
// requires provisioning an R2 bucket and binding it (see OpenNext docs
// for `r2IncrementalCache`). Add it later if ISR / fetch caching at
// the edge becomes a hard requirement.
//
// If you ever add overrides (queue, image optimizer, etc.), extend
// this object. Keep the default export and do not change the file
// name — OpenNext's CLI looks for `open-next.config.ts` at the root.

import { defineCloudflareConfig } from "@opennextjs/cloudflare";

export default defineCloudflareConfig({});
Loading