Skip to content

Commit 1dfa3a1

Browse files
committed
feat: auto-enable remote bindings when IDs configured
1 parent cd53e00 commit 1dfa3a1

18 files changed

Lines changed: 643 additions & 15 deletions

File tree

build.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ export default defineBuildConfig({
5050
input: 'src/cache/runtime/',
5151
outDir: 'dist/cache/runtime',
5252
builder: 'mkdist'
53+
},
54+
// Remote (cloudflare dev bindings)
55+
{
56+
input: 'src/remote/runtime/',
57+
outDir: 'dist/remote/runtime',
58+
builder: 'mkdist'
5359
}
5460
]
5561
})

docs/content/docs/1.getting-started/3.deploy.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ navigation.title: Deploy
44
description: Learn how to host a full-stack Nuxt application with minimal configuration.
55
---
66

7+
NuxtHub supports multiple cloud providers including Vercel, Cloudflare Workers, and any Node.js hosting platform. Each provider offers different storage options that NuxtHub automatically configures.
8+
79
## Vercel
810

911
::tip
@@ -56,10 +58,56 @@ export default defineNuxtConfig({
5658
})
5759
```
5860

61+
::important{title="Remote Bindings in Development"}
62+
When you configure a Cloudflare binding ID (`databaseId`, `namespaceId`, `bucketName`, or `hyperdriveId`), NuxtHub automatically connects to your remote Cloudflare resources during local development. This uses Wrangler's [`getPlatformProxy`](https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy) to provide real bindings in dev mode.
63+
64+
Ensure `wrangler` is installed as a dev dependency: `npx nypm add -D wrangler`
65+
::
66+
5967
::tip
6068
See a working example at [onmax/repros/nuxthub-716](https://github.com/onmax/repros/tree/main/nuxthub-716) — deployed without a `wrangler.toml` file.
6169
::
6270

71+
### Local-Only Development
72+
73+
If you prefer to use local storage during development and only connect to Cloudflare resources in production, use Nuxt's `$production` environment override. This pattern ensures binding IDs are only applied when building for production:
74+
75+
```ts [nuxt.config.ts]
76+
export default defineNuxtConfig({
77+
hub: {
78+
db: 'sqlite',
79+
kv: true,
80+
blob: true
81+
},
82+
// Only use remote bindings in production
83+
$production: {
84+
hub: {
85+
db: {
86+
dialect: 'sqlite',
87+
driver: 'd1',
88+
connection: { databaseId: '<database-id>' }
89+
},
90+
kv: {
91+
driver: 'cloudflare-kv-binding',
92+
namespaceId: '<kv-namespace-id>'
93+
},
94+
blob: {
95+
driver: 'cloudflare-r2',
96+
bucketName: '<bucket-name>'
97+
}
98+
}
99+
}
100+
})
101+
```
102+
103+
With this configuration:
104+
- **Development**: Uses local storage (SQLite file, filesystem) in `.data/` directory
105+
- **Production**: Connects to your Cloudflare D1, KV, and R2 resources
106+
107+
::note
108+
The `$production` override merges with your base config. You only need to specify the properties that differ in production.
109+
::
110+
63111
### Deploy
64112

65113
Create a [Cloudflare Workers project](https://dash.cloudflare.com/?to=/:account/workers-and-pages/create) and link your GitHub or GitLab repository. NuxtHub auto-configures bindings from your `nuxt.config.ts` during build.

docs/content/docs/1.getting-started/4.migration.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ You can visit [legacy.hub.nuxt.com](https://legacy.hub.nuxt.com) to read the doc
3333
| Blob access | `hubBlob()` | `blob` from `hub:blob` |
3434
| KV access | `hubKV()` | `kv` from `hub:kv` |
3535
| AI & AutoRAG | `hubAI()` | Removed (use [AI SDK](https://ai-sdk.dev)) |
36+
| Remote storage | `hub.projectUrl` + `hub.projectSecretKey` | Auto-enabled when binding IDs configured |
3637
| NuxtHub Admin | Supported | Deprecated (sunset Dec 31, 2025) |
3738
| `nuxthub deploy` | Supported | Deprecated (sunset Jan 31, 2026) |
3839

@@ -93,6 +94,40 @@ export default defineNuxtConfig({
9394
})
9495
```
9596

97+
### Remote Development
98+
99+
The `projectUrl` and `projectSecretKey` options have been removed. NuxtHub now auto-detects when you want to connect to remote Cloudflare bindings during local development.
100+
101+
When binding IDs are present in your config (e.g., `databaseId`, `namespaceId`, `bucketName`), NuxtHub will:
102+
1. Detect the remote binding configuration
103+
2. Show a warning about connecting to production resources
104+
3. Generate a temporary `wrangler.toml` with `remote = true` for all bindings
105+
106+
```ts [nuxt.config.ts]
107+
export default defineNuxtConfig({
108+
hub: {
109+
db: {
110+
dialect: 'sqlite',
111+
driver: 'd1',
112+
connection: { databaseId: '<database-id>' } // Triggers remote binding
113+
},
114+
kv: { driver: 'cloudflare-kv-binding', namespaceId: '<kv-id>' },
115+
blob: { driver: 'cloudflare-r2', bucketName: '<bucket-name>' }
116+
}
117+
})
118+
```
119+
120+
::warning
121+
Remote bindings connect to **production resources**. Seeds and migrations will run against production data. Confirm only when intentional.
122+
::
123+
124+
::tip
125+
Use `$production` pattern to only enable binding IDs in production, keeping local development isolated:
126+
```ts
127+
connection: { databaseId: { $production: '<database-id>' } }
128+
```
129+
::
130+
96131
## Code Migration
97132

98133
### Database Access
@@ -380,6 +415,7 @@ Replace `npx nuxthub deploy` with your provider's deployment method:
380415
- [ ] Replace `hubBlob()` calls with `blob` from `hub:blob`
381416
- [ ] Replace `hubKV()` calls with `kv` from `hub:kv`
382417
- [ ] Remove AI/AutoRAG usage or migrate to AI SDK
418+
- [ ] Remove `hub.projectUrl` and `hub.projectSecretKey` (remote bindings auto-enable when IDs configured)
383419
- [ ] For Cloudflare: Configure resource IDs in `nuxt.config.ts` (v0.10.3+) OR create manual `wrangler.jsonc`
384420
- [ ] For Vercel: Add storage from dashboard and install required packages
385421
- [ ] Update CI/CD from NuxtHub GitHub Action to provider's deployment (Workers/Pages CI, Vercel Git integration, etc.)

docs/content/docs/2.database/1.index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Install Drizzle ORM, Drizzle Kit, and the appropriate driver(s) for the database
2222
- Uses `postgres-js` driver if you set `DATABASE_URL`, `POSTGRES_URL`, or `POSTGRESQL_URL` environment variable.
2323
- Use `neon-http` driver with `@neondatabase/serverless` for [Neon](https://neon.com) serverless PostgreSQL.
2424
::
25+
::note
26+
Setting a `DATABASE_URL` locally connects to your remote PostgreSQL database during development.
27+
::
2528
:::
2629
:::tabs-item{label="MySQL" icon="i-simple-icons-mysql"}
2730
:pm-install{name="drizzle-orm drizzle-kit mysql2"}
@@ -30,6 +33,9 @@ Install Drizzle ORM, Drizzle Kit, and the appropriate driver(s) for the database
3033
- Uses `mysql2` driver if you set `DATABASE_URL` or `MYSQL_URL` environment variable.
3134
- Requires environment variable (no local fallback)
3235
::
36+
::note
37+
Setting a `DATABASE_URL` or `MYSQL_URL` locally connects to your remote MySQL database during development.
38+
::
3339
:::
3440
:::tabs-item{label="SQLite" icon="i-simple-icons-sqlite"}
3541
:pm-install{name="drizzle-orm drizzle-kit @libsql/client"}
@@ -38,9 +44,15 @@ Install Drizzle ORM, Drizzle Kit, and the appropriate driver(s) for the database
3844
- Uses `libsql` driver for [Turso](https://turso.tech) if you set `TURSO_DATABASE_URL` and `TURSO_AUTH_TOKEN` environment variables.
3945
- Uses `libsql` locally with file at `.data/db/sqlite.db` if no environment variables are set.
4046
::
47+
::note
48+
Setting `TURSO_DATABASE_URL` locally connects to your remote Turso database during development.
49+
::
4150
::tip{to="/docs/getting-started/deploy#cloudflare"}
4251
For Cloudflare D1, configure the database ID in your `nuxt.config.ts` and NuxtHub auto-generates the wrangler bindings.
4352
::
53+
::warning
54+
Configuring a `databaseId` connects to your remote Cloudflare D1 database during development. Use the [`$production` pattern](/docs/getting-started/deploy#local-only-development) to keep development local-only.
55+
::
4456
:::
4557
::
4658

docs/content/docs/3.blob/1.index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ By default, if NuxtHub cannot detect a driver, files are stored locally in the `
6666
S3_REGION=your-region
6767
S3_ENDPOINT=your-endpoint # (optional)
6868
```
69+
70+
::note
71+
Setting these credentials locally connects to your remote S3 bucket during development.
72+
::
6973
:::
7074
:::tabs-item{label="Vercel Blob" icon="i-simple-icons-vercel"}
7175

@@ -86,6 +90,10 @@ By default, if NuxtHub cannot detect a driver, files are stored locally in the `
8690
```bash [.env]
8791
BLOB_READ_WRITE_TOKEN=your-token
8892
```
93+
94+
::note
95+
Setting this token locally connects to your remote Vercel Blob store during development.
96+
::
8997
:::
9098

9199
:::tabs-item{label="Cloudflare R2" icon="i-simple-icons-cloudflare"}
@@ -107,6 +115,10 @@ By default, if NuxtHub cannot detect a driver, files are stored locally in the `
107115
Learn more about R2 bindings on Cloudflare's documentation.
108116
::
109117

118+
::warning
119+
Configuring a `bucketName` connects to your remote Cloudflare R2 bucket during development. Use the [`$production` pattern](/docs/getting-started/deploy#local-only-development) to keep development local-only.
120+
::
121+
110122
::note
111123
To use Cloudflare R2 without hosting on Cloudflare Workers, use the [Cloudflare R2 via S3 API](https://developers.cloudflare.com/r2/api/s3/api/).
112124
::

docs/content/docs/4.kv/1.index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ When building the Nuxt app, NuxtHub automatically configures the key-value stora
3535
UPSTASH_REDIS_REST_TOKEN=...
3636
```
3737

38+
::note
39+
Setting these credentials locally connects to your remote Upstash Redis during development.
40+
::
41+
3842
::tip
3943
When deploying to Vercel, we automatically detect if `KV_REST_API_URL` and `KV_REST_API_TOKEN` environment variables are set, and use them to configure the Upstash Redis connection.
4044
::
@@ -73,6 +77,10 @@ When building the Nuxt app, NuxtHub automatically configures the key-value stora
7377
Learn more about KV bindings on Cloudflare's documentation.
7478
::
7579

80+
::warning
81+
Configuring a `namespaceId` connects to your remote Cloudflare KV namespace during development. Use the [`$production` pattern](/docs/getting-started/deploy#local-only-development) to keep development local-only.
82+
::
83+
7684
:::
7785

7886
:::tabs-item{label="Deno KV" icon="i-simple-icons-deno"}

docs/content/docs/5.cache/1.index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ NuxtHub automatically configures the cache storage driver based on your hosting
4545
::callout{to="https://developers.cloudflare.com/kv/concepts/kv-bindings/"}
4646
Learn more about KV bindings on Cloudflare's documentation.
4747
::
48+
49+
::warning
50+
Configuring a `namespaceId` connects to your remote Cloudflare KV namespace during development. Use the [`$production` pattern](/docs/getting-started/deploy#local-only-development) to keep development local-only.
51+
::
4852
:::
4953

5054
:::tabs-item{label="Other" icon="i-simple-icons-nodedotjs" class="p-4"}

src/blob/setup.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ export function resolveBlobConfig(hub: HubConfig, deps: Record<string, string>):
4949
}) as ResolvedBlobConfig
5050
}
5151

52-
// Cloudflare R2
53-
if (hub.hosting.includes('cloudflare')) {
52+
// Cloudflare R2 (production or dev with bucketName)
53+
const blobConfig = typeof hub.blob === 'object' ? hub.blob : {}
54+
if (hub.hosting.includes('cloudflare') || ('bucketName' in blobConfig && blobConfig.bucketName)) {
5455
return defu(hub.blob, {
5556
driver: 'cloudflare-r2',
5657
binding: 'BLOB'

src/cache/setup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ export function resolveCacheConfig(hub: HubConfig): ResolvedCacheConfig | false
2222
return userConfig as ResolvedCacheConfig
2323
}
2424

25-
// Cloudflare KV cache binding
26-
if (hub.hosting.includes('cloudflare')) {
25+
// Cloudflare KV cache binding (production or dev with namespaceId)
26+
if (hub.hosting.includes('cloudflare') || userConfig.namespaceId) {
2727
return defu(userConfig, {
2828
driver: 'cloudflare-kv-binding',
2929
binding: 'CACHE'

src/db/setup.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ export async function resolveDatabaseConfig(nuxt: Nuxt, hub: HubConfig): Promise
4949
}
5050
break
5151
}
52-
// Cloudflare D1
53-
if (hub.hosting.includes('cloudflare')) {
52+
// Cloudflare D1 (production or dev with databaseId)
53+
if (hub.hosting.includes('cloudflare') || config.connection?.databaseId) {
5454
config.driver = 'd1'
5555
break
5656
}
@@ -61,8 +61,8 @@ export async function resolveDatabaseConfig(nuxt: Nuxt, hub: HubConfig): Promise
6161
break
6262
}
6363
case 'postgresql': {
64-
// Cloudflare Hyperdrive with explicit hyperdriveId
65-
if (hub.hosting.includes('cloudflare') && config.connection?.hyperdriveId && !config.driver) {
64+
// Cloudflare Hyperdrive with explicit hyperdriveId (production or dev)
65+
if (config.connection?.hyperdriveId && !config.driver) {
6666
config.driver = 'postgres-js'
6767
break
6868
}
@@ -81,8 +81,8 @@ export async function resolveDatabaseConfig(nuxt: Nuxt, hub: HubConfig): Promise
8181
break
8282
}
8383
case 'mysql': {
84-
// Cloudflare Hyperdrive with explicit hyperdriveId
85-
if (hub.hosting.includes('cloudflare') && config.connection?.hyperdriveId && !config.driver) {
84+
// Cloudflare Hyperdrive with explicit hyperdriveId (production or dev)
85+
if (config.connection?.hyperdriveId && !config.driver) {
8686
config.driver = 'mysql2'
8787
break
8888
}
@@ -403,7 +403,7 @@ const db = drizzle(d1HttpDriver, { schema${casingOption} })
403403
export { db, schema }
404404
`
405405
}
406-
if (['postgres-js', 'mysql2'].includes(driver) && hub.hosting.includes('cloudflare')) {
406+
if (['postgres-js', 'mysql2'].includes(driver) && (hub.hosting.includes('cloudflare') || connection?.hyperdriveId)) {
407407
// Hyperdrive requires lazy binding access - bindings only available in request context on CF Workers
408408
const bindingName = driver === 'postgres-js' ? 'POSTGRES' : 'MYSQL'
409409
drizzleOrmContent = `import { drizzle } from 'drizzle-orm/${driver}'

0 commit comments

Comments
 (0)