diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml
index 51feff26..fc72e7b6 100644
--- a/.github/workflows/autofix.yml
+++ b/.github/workflows/autofix.yml
@@ -11,10 +11,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - run: corepack enable
+ - run: npm i -g --force corepack && corepack enable
- uses: actions/setup-node@v4
with:
- node-version: 20
+ node-version: 22
cache: "pnpm"
- name: Install dependencies
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0ad2d911..1d350877 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,10 +31,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - run: corepack enable
+ - run: npm i -g --force corepack && corepack enable
- uses: actions/setup-node@v4
with:
- node-version: 20
+ node-version: 22
cache: "pnpm"
- name: Install dependencies
diff --git a/docs/components/content/PricingTable.vue b/docs/components/content/PricingTable.vue
new file mode 100644
index 00000000..03ec043f
--- /dev/null
+++ b/docs/components/content/PricingTable.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/content/1.docs/2.features/ai.md b/docs/content/1.docs/2.features/ai.md
index 031cea58..17590625 100644
--- a/docs/content/1.docs/2.features/ai.md
+++ b/docs/content/1.docs/2.features/ai.md
@@ -226,3 +226,7 @@ Learn more about the [`useChat()` Vue composable](https://sdk.vercel.ai/docs/ref
::callout
Check out our [`pages/ai.vue` full example](https://github.com/nuxt-hub/core/blob/main/playground/app/pages/ai.vue) with Nuxt UI & [Nuxt MDC](https://github.com/nuxt-modules/mdc).
::
+
+## Pricing
+
+:pricing-table{:tabs='["AI"]'}
diff --git a/docs/content/1.docs/2.features/blob.md b/docs/content/1.docs/2.features/blob.md
index 53657d8c..f5073260 100644
--- a/docs/content/1.docs/2.features/blob.md
+++ b/docs/content/1.docs/2.features/blob.md
@@ -984,3 +984,8 @@ That's it! You can now upload files to R2 using the presigned URLs.
::callout
Read more about presigned URLs on Cloudflare's [official documentation](https://developers.cloudflare.com/r2/api/s3/presigned-urls/).
::
+
+## Pricing
+
+:pricing-table{:tabs='["Blob"]'}
+
diff --git a/docs/content/1.docs/2.features/cache.md b/docs/content/1.docs/2.features/cache.md
index 0e0d0eef..9c4ccd0f 100644
--- a/docs/content/1.docs/2.features/cache.md
+++ b/docs/content/1.docs/2.features/cache.md
@@ -4,6 +4,8 @@ navigation.title: Cache
description: Learn how to cache Nuxt pages, API routes and functions in with NuxtHub cache storage.
---
+NuxtHub Cache is powered by [Nitro's cache storage](https://nitro.unjs.io/guide/cache#customize-cache-storage) and uses [Cloudflare Workers KV](https://developers.cloudflare.com/kv) as the cache storage. It allows you to cache API routes, server functions, and pages in your application.
+
## Getting Started
Enable the cache storage in your NuxtHub project by adding the `cache` property to the `hub` object in your `nuxt.config.ts` file.
@@ -81,6 +83,27 @@ It is important to note that the `event` argument should always be the first arg
[Read more about this in the Nitro docs](https://nitro.unjs.io/guide/cache#edge-workers).
::
+## Routes Caching
+
+You can enable route caching in your `nuxt.config.ts` file.
+
+```ts [nuxt.config.ts]
+export default defineNuxtConfig({
+ routeRules: {
+ '/blog/**': {
+ cache: {
+ maxAge: 60 * 60,
+ // other options like name, group, swr...
+ }
+ }
+ }
+})
+```
+
+::note
+Read more about [Nuxt's route rules](https://nuxt.com/docs/guide/concepts/rendering#hybrid-rendering).
+::
+
## Cache Invalidation
When using the `defineCachedFunction` or `defineCachedEventHandler` functions, the cache key is generated using the following pattern:
@@ -91,7 +114,7 @@ When using the `defineCachedFunction` or `defineCachedEventHandler` functions, t
The defaults are:
- `group`: `'nitro'`
-- `name`: `'handlers'` for api routes and `'functions'` for server functions
+- `name`: `'handlers'` for API routes, `'functions'` for server functions, or `'routes'` for route handlers
For example, the following function:
@@ -99,7 +122,7 @@ For example, the following function:
const getAccessToken = defineCachedFunction(() => {
return String(Date.now())
}, {
- maxAge: 10,
+ maxAge: 60,
name: 'getAccessToken',
getKey: () => 'default'
})
@@ -111,12 +134,20 @@ Will generate the following cache key:
nitro:functions:getAccessToken:default.json
```
-You can invalidate the cached function entry with:
+You can invalidate the cached function entry from your storage using cache key.
```ts
await useStorage('cache').removeItem('nitro:functions:getAccessToken:default.json')
```
+You can use the `group` and `name` options to invalidate multiple cache entries based on their prefixes.
+
+```ts
+// Gets all keys that start with nitro:handlers
+await useStorage('cache').clear('nitro:handlers')
+```
+
+
::note{to="https://nitro.unjs.io/guide/cache"}
Read more about Nitro Cache.
::
@@ -125,6 +156,29 @@ Read more about Nitro Cache.
As NuxtHub leverages Cloudflare Workers KV to store your cache entries, we leverage the [`expiration` property](https://developers.cloudflare.com/kv/api/write-key-value-pairs/#expiring-keys) of the KV binding to handle the cache expiration.
+By default, `stale-while-revalidate` behavior is enabled. If an expired cache entry is requested, the stale value will be served while the cache is asynchronously refreshed. This also means that all cache entries will remain in your KV namespace until they are manually invalidated/deleted.
+
+To disable this behavior, set `swr` to `false` when defining a cache rule. This will delete the cache entry once `maxAge` is reached.
+
+```ts [nuxt.config.ts]
+export default defineNuxtConfig({
+ nitro: {
+ routeRules: {
+ '/blog/**': {
+ cache: {
+ maxAge: 60 * 60,
+ swr: false
+ // other options like name and group...
+ }
+ }
+ }
+})
+```
+
::note
If you set an expiration (`maxAge`) lower than `60` seconds, NuxtHub will set the KV entry expiration to `60` seconds in the future (Cloudflare KV limitation) so it can be removed automatically.
::
+
+## Pricing
+
+:pricing-table{:tabs='["KV"]'}
diff --git a/docs/content/1.docs/2.features/database.md b/docs/content/1.docs/2.features/database.md
index 2a9f4ee7..7b49be70 100644
--- a/docs/content/1.docs/2.features/database.md
+++ b/docs/content/1.docs/2.features/database.md
@@ -4,6 +4,8 @@ navigation.title: Database
description: Access a SQL database in your Nuxt application to store and retrieve relational data.
---
+NuxtHub Database uses [Cloudflare D1](https://developers.cloudflare.com/d1/), a managed, serverless database built on SQLite to store and retrieve relational data.
+
## Getting Started
Enable the database in your NuxtHub project by adding the `database` property to the `hub` object in your `nuxt.config.ts` file.
@@ -24,6 +26,8 @@ This option will use Cloudflare platform proxy in development and automatically
Checkout our [Drizzle ORM recipe](/docs/recipes/drizzle) to get started with the database by providing a schema and migrations.
::
+During local development, you can view and edit your database in the Nuxt DevTools. Once your project is deployed, you can inspect the database in the NuxtHub Admin Dashboard.
+
::tabs
::div{label="Nuxt DevTools"}
:nuxt-img{src="/images/landing/nuxt-devtools-database.png" alt="Nuxt DevTools Database" width="915" height="515" data-zoom-src="/images/landing/nuxt-devtools-database.png"}
@@ -59,22 +63,35 @@ Best practice is to use prepared statements which are precompiled objects used b
### `bind()`
-Binds parameters to a prepared statement.
+Binds parameters to a prepared statement, allowing you to pass dynamic values to the query.
```ts
const stmt = db.prepare('SELECT * FROM users WHERE name = ?1')
stmt.bind('Evan You')
+
+// SELECT * FROM users WHERE name = 'Evan You'
```
-::note
-The `?` character followed by a number (1-999) represents an ordered parameter. The number represents the position of the parameter when calling `.bind(...params)`.
-::
+The `?` character followed by a number (1-999) represents an ordered parameter. The number represents the position of the parameter when calling `.bind(...params)`.
```ts
const stmt = db
.prepare('SELECT * FROM users WHERE name = ?2 AND age = ?1')
.bind(3, 'Leo Chopin')
+// SELECT * FROM users WHERE name = 'Leo Chopin' AND age = 3
+```
+
+If you instead use anonymous parameters (without a number), the values passed to `bind` will be assigned in order to the `?` placeholders in the query.
+
+It's recommended to use ordered parameters to improve maintainable and ensure that removing or reordering parameters will not break your queries.
+
+```ts
+const stmt = db
+ .prepare('SELECT * FROM users WHERE name = ? AND age = ?')
+ .bind('Leo Chopin', 3)
+
+// SELECT * FROM users WHERE name = 'Leo Chopin' AND age = 3
```
### `all()`
@@ -190,7 +207,9 @@ console.log(result)
### `batch()`
-Sends multiple SQL statements inside a single call to the database. This can have a huge performance impact as it reduces latency from network round trips to the database. Each statement in the list will execute and commit, sequentially, non-concurrently and return the results in the same order.
+Sends multiple SQL statements inside a single call to the database. This can have a huge performance impact by reducing latency caused by multiple network round trips to the database. Each statement in the list will execute/commit sequentially and non-concurrently before returning the results in the same order.
+
+`batch` acts as a SQL transaction, meaning that if any statement fails, the entire transaction is aborted and rolled back.
```ts
const [info1, info2] = await db.batch([
@@ -222,7 +241,7 @@ The object returned is the same as the [`.all()`](#all) method.
Executes one or more queries directly without prepared statements or parameters binding. The input can be one or multiple queries separated by \n.
-If an error occurs, an exception is thrown with the query and error messages, execution stops and further statements are not executed.
+If an error occurs, an exception is thrown with the query and error messages, execution stops, and further queries are not executed.
```ts
const result = await hubDatabase().exec(`CREATE TABLE IF NOT EXISTS frameworks (id INTEGER PRIMARY KEY, name TEXT NOT NULL, year INTEGER NOT NULL DEFAULT 0)`)
@@ -236,9 +255,52 @@ console.log(result)
```
::callout
-This method can have poorer performance (prepared statements can be reused in some cases) and, more importantly, is less safe. Only use this method for maintenance and one-shot tasks (for example, migration jobs). The input can be one or multiple queries separated by \n.
+This method can have poorer performance (prepared statements can be reused in some cases) and, more importantly, is less safe. Only use this method for maintenance and one-shot tasks (for example, migration jobs).
+::
+
+## Working with JSON
+
+Cloudflare D1 supports querying and parsing JSON data. This can improve performance by reducing the number of round trips to your database. Instead of querying a JSON column, extracting the data you need, and using that data to make another query, you can do all of this work in a single query by using JSON functions.
+
+JSON columns are stored as `TEXT` columns in your database.
+
+```ts
+const framework = {
+ name: 'Nuxt',
+ year: 2016,
+ projects: [
+ 'NuxtHub',
+ 'Nuxt UI'
+ ]
+}
+
+await hubDatabase()
+ .prepare('INSERT INTO frameworks (info) VALUES (?1)')
+ .bind(JSON.stringify(framework))
+ .run()
+```
+
+Then, using D1's [JSON functions](https://developers.cloudflare.com/d1/sql-api/query-json/), which are built on the [SQLite JSON extension](https://www.sqlite.org/json1.html), you can make queries using the data in your JSON column.
+
+```ts
+const framework = await db.prepare('SELECT * FROM frameworks WHERE (json_extract(info, "$.name") = "Nuxt")').first()
+console.log(framework)
+/*
+{
+ "id": 1,
+ "info": "{\"name\":\"Nuxt\",\"year\":2016,\"projects\":[\"NuxtHub\",\"Nuxt UI\"]}"
+}
+*/
+```
+
+::callout
+For an in-depth guide on querying JSON and a list of all supported functions, see [Cloudlare's Query JSON documentation](https://developers.cloudflare.com/d1/sql-api/query-json/#generated-columns).
::
+## Using an ORM
+
+Instead of using `hubDatabase()` to make interact with your database, you can use an ORM like [Drizzle ORM](/docs/recipes/drizzle). This can improve the developer experience by providing a type-safe API, migrations, and more.
+
## Database Migrations
Database migrations provide version control for your database schema. They track changes and ensure consistent schema evolution across all environments through incremental updates. NuxtHub supports SQL migration files (`.sql`).
@@ -318,14 +380,26 @@ npx nuxthub database migrations create
Migration names must only contain alphanumeric characters and `-` (spaces are converted to `-`).
::
-Migration files are created in `server/database/migrations/`.
+Migration files are created in `server/database/migrations/` and are prefixed by an auto-incrementing sequence number. This migration number is used to determine the order in which migrations are run.
```bash [Example]
> npx nuxthub database migrations create create-todos
✔ Created ./server/database/migrations/0001_create-todos.sql
```
-After creation, add your SQL queries to modify the database schema.
+After creation, add your SQL queries to modify the database schema. For example, migrations should be used to create tables, add/delete/modify columns, and add/remove indexes.
+
+```sql [0001_create-todos.sql]
+-- Migration number: 0001 2025-01-30T17:17:37.252Z
+
+CREATE TABLE `todos` (
+ `id` integer PRIMARY KEY NOT NULL,
+ `user_id` integer NOT NULL,
+ `title` text NOT NULL,
+ `completed` integer DEFAULT 0 NOT NULL,
+ `created_at` integer NOT NULL
+);
+```
::note{to="/docs/recipes/drizzle#npm-run-dbgenerate"}
With [Drizzle ORM](/docs/recipes/drizzle), migrations are automatically created when you run `npx drizzle-kit generate`.
@@ -417,7 +491,7 @@ These queries run after all migrations are applied but are not tracked in the `_
### Foreign Key Constraints
-If you are using [Drizzle ORM](/docs/recipes/drizzle) to generate your database migrations, note that is uses `PRAGMA foreign_keys = ON | OFF;` in the generated migration files. This is not supported by Cloudflare D1 as they support instead [defer foreign key constraints](https://developers.cloudflare.com/d1/sql-api/foreign-keys/#defer-foreign-key-constraints).
+If you are using [Drizzle ORM](/docs/recipes/drizzle) to generate your database migrations, your generated migration files will use `PRAGMA foreign_keys = ON | OFF;`. This is not supported by Cloudflare D1. Instead, they support [defer foreign key constraints](https://developers.cloudflare.com/d1/sql-api/foreign-keys/#defer-foreign-key-constraints).
You need to update your migration file to use `PRAGMA defer_foreign_keys = on|off;` instead:
@@ -430,3 +504,14 @@ ALTER TABLE ...
-PRAGMA foreign_keys = ON;
+PRAGMA defer_foreign_keys = off;
```
+
+## Limits
+
+- The maximum database size is 10 GB
+- The maximum number of columns per table is 100
+
+See all of the [D1 Limits](https://developers.cloudflare.com/d1/platform/limits/)
+
+## Pricing
+
+:pricing-table{:tabs='["DB"]'}
diff --git a/docs/content/1.docs/2.features/kv.md b/docs/content/1.docs/2.features/kv.md
index de5f51f3..b10984b4 100644
--- a/docs/content/1.docs/2.features/kv.md
+++ b/docs/content/1.docs/2.features/kv.md
@@ -3,6 +3,13 @@ title: Key Value Storage
navigation.title: Key Value
description: Add a global, low-latency key-value data storage to your Nuxt application.
---
+NuxtHub Key Value Storage uses [Unstorage](https://unstorage.unjs.io) with [Cloudflare Workers KV](https://developers.cloudflare.com/kv) to store key-value data.
+
+
+## Use Cases
+- **Frequently Read Data** - values are cached in regional data centers closer to the user so multiple requests from the same region will be faster
+- **Per-Object Expiration** - passing a `ttl` when writing object will delete it after a certain amount of time
+- **Eventual Consistency** - cached values are eventually consistent and may take up to 60 seconds to update across all regions, allowing for improved performance when strong consistency is not required
## Getting Started
@@ -20,6 +27,8 @@ export default defineNuxtConfig({
This option will use Cloudflare platform proxy in development and automatically create a [Cloudflare Workers KV](https://developers.cloudflare.com/kv) namespace for your project when you [deploy it](/docs/getting-started/deploy).
::
+You can inspect your KV namespace during local development in the Nuxt DevTools or after a deployment using the NuxtHub Admin Dashboard.
+
::tabs
::div{label="Nuxt DevTools"}
:nuxt-img{src="/images/landing/nuxt-devtools-kv.png" alt="Nuxt DevTools KV" width="915" height="515" data-zoom-src="/images/landing/nuxt-devtools-kv.png"}
@@ -29,63 +38,30 @@ This option will use Cloudflare platform proxy in development and automatically
::
::
-## List all keys
+## How KV Works
-Retrieves all keys from the KV storage.
+NuxtHub uses [Cloudflare Workers KV](https://developers.cloudflare.com/kv) to store key-value data a few centralized data
+centers. Then, when data is requested, it will cache the responses in regional data centers closer to the user to speed up future requests coming from the same region.
-```ts
-const keys = await hubKV().keys()
-/*
-[
- 'react',
- 'react:gatsby',
- 'react:next',
- 'vue',
- 'vue:nuxt',
- 'vue:quasar'
-]
-```
+This caching means that KV is optimized for high-read use cases, but it also means that changes like editing or deleting data are **eventually consistent** and may take up to 60 seconds to propagate to all regions. Even if a request is made for a key that does not exist in the KV namespace, that result will be cached for up to 60 seconds.
-To get the keys starting with a specific prefix, you can pass the prefix as an argument.
+If you need a strongly consistent data model, where changes are immediately visible to all users, a [NuxtHub database](/docs/features/database) may be a better fit.
-```ts
-const vueKeys = await hubKV().keys('vue')
-/*
-[
- 'vue:nuxt',
- 'vue:quasar'
-]
-*/
-```
+To learn more about how KV works, check out the [Cloudflare KV documentation](https://developers.cloudflare.com/kv/concepts/how-kv-works/).
-::important
-We recommend to use prefixes for better organization and performance as `keys()` will scan the entire namespace.
-::
-
-## Get an item
+## `hubKV()`
-Retrieves an item from the Key-Value storage.
+`hubKV()` is a server composable that returns an [Unstorage](https://unstorage.unjs.io) instance with [Cloudflare KV binding](https://unstorage.unjs.io/drivers/cloudflare#cloudflare-kv-binding) as the [driver](https://unstorage.unjs.io/drivers/cloudflare).
-```ts
-const vue = await hubKV().get('vue')
-/*
-{
- year: 2014
-}
-*/
-```
-## Set an item
+### Set an item
Puts an item in the storage.
```ts
await hubKV().set('vue', { year: 2014 })
-```
-You can delimit the key with a `:` to create a namespace:
-
-```ts
+// using prefixes to organize your KV namespace, useful for the `keys` operation
await hubKV().set('vue:nuxt', { year: 2016 })
```
@@ -93,24 +69,17 @@ await hubKV().set('vue:nuxt', { year: 2016 })
The maximum size of a value is 25 MiB and the maximum length of a key is 512 bytes.
::
-### Expiration
+#### Expiration
+
+By default, items in your KV namespace will never expire. You can delete them manually using the [`del()`](#delete-an-item) method or set a TTL (time to live) in seconds.
-You can also set a TTL (time to live) in seconds:
+The item will be deleted after the TTL has expired. The `ttl` option maps to Cloudflare's [`expirationTtl`](https://developers.cloudflare.com/kv/api/write-key-value-pairs/#reference) option. Values that have recently been read will continue to return the cached value for up to 60 seconds and may not be immediately deleted for all regions.
```ts
await hubKV().set('vue:nuxt', { year: 2016 }, { ttl: 60 })
```
-The item will be deleted after the TTL has expired.
-
-## Has an item
+
+### Get an item
+
+Retrieves an item from the Key-Value storage.
+
+```ts
+const vue = await hubKV().get('vue')
+/*
+{
+ year: 2014
+}
+*/
+```
+
+
+### Has an item
Checks if an item exists in the storage.
```ts
-const hasAngular = await hubKV().has('angular')
+const hasAngular = await hubKV().has('angular') // false
+const hasVue = await hubKV().has('vue') // true
```
-## Delete an item
+### Delete an item
-Delete an item from the storage.
+Delete an item with the given key from the storage.
```ts
await hubKV().del('react')
```
+### Clear the KV namespace
+
+Deletes all items from the KV namespace..
+
+```ts
+await hubKV().clear()
+```
+
+To delete all items for a specific prefix, you can pass the prefix as an argument. We recommend using prefixes for better organization in your KV namespace.
+
+```ts
+await hubKV().clear('react')
+```
+
+### List all keys
+
+Retrieves all keys from the KV storage.
+
+```ts
+const keys = await hubKV().keys()
+/*
+[
+ 'react',
+ 'react:gatsby',
+ 'react:next',
+ 'vue',
+ 'vue:nuxt',
+ 'vue:quasar'
+]
+```
+
+To get the keys starting with a specific prefix, you can pass the prefix as an argument. We recommend using prefixes for better organization in your KV namespace.
+
+```ts
+const vueKeys = await hubKV().keys('vue')
+/*
+[
+ 'vue:nuxt',
+ 'vue:quasar'
+]
+*/
+```
+
## Limits
- The maximum size of a value is 25 MiB.
- The maximum length of a key is 512 bytes.
- The TTL must be at least 60 seconds.
+- There is a maximum of 1 write to the same key per second ([KV write rate limit](https://developers.cloudflare.com/kv/api/write-key-value-pairs/#limits-to-kv-writes-to-the-same-key)).
Learn more about [Cloudflare KV limits](https://developers.cloudflare.com/kv/platform/limits/).
@@ -152,3 +181,7 @@ Learn more about [Cloudflare KV limits](https://developers.cloudflare.com/kv/pla
## Learn More
`hubKV()` is an instance of [unstorage](https://unstorage.unjs.io/guide#interface) with the [Cloudflare KV binding](https://unstorage.unjs.io/drivers/cloudflare#cloudflare-kv-binding) driver.
+
+## Pricing
+
+:pricing-table{:tabs='["KV"]'}
diff --git a/docs/content/1.docs/2.features/vectorize.md b/docs/content/1.docs/2.features/vectorize.md
index 2706c12d..3e645203 100644
--- a/docs/content/1.docs/2.features/vectorize.md
+++ b/docs/content/1.docs/2.features/vectorize.md
@@ -935,3 +935,7 @@ export default defineTask({
```
+
+## Pricing
+
+:pricing-table{:tabs='["Vectorize"]'}
diff --git a/docs/content/3.pricing.yml b/docs/content/3.pricing.yml
index ff596f9d..69a813d3 100644
--- a/docs/content/3.pricing.yml
+++ b/docs/content/3.pricing.yml
@@ -96,163 +96,6 @@ pricing:
cloudflare:
title: 'Cloudflare pricing'
description: 'You can start for free with Cloudflare and seamlessly upgrade to Workers Paid plan when needed for $5/month.'
- plans:
- - label: 'App'
- icon: i-lucide-panels-top-left
- buttons:
- - label: 'Cloudflare Workers pricing'
- to: 'https://developers.cloudflare.com/workers/platform/pricing/'
- columns:
- - key: 'title'
- label: ''
- - key: 'free'
- label: 'Free'
- - key: 'paid'
- label: 'Workers Paid ($5/month)'
- rows:
- - title: 'Requests'
- free: '100,000 / day'
- paid: '10 million / month + $0.30/million'
- - title: 'Worker size'
- free: '3 MB'
- paid: '10 MB'
- - title: 'CPU time'
- free: '10ms per invocation'
- paid: '30s per invocation
30 million CPU ms + $0.02/million'
- - title: 'CPU milliseconds'
- free: '100'
- paid: '30 million'
- - title: 'Applications'
- free: '100'
- paid: '500'
- - label: 'DB'
- icon: i-lucide-database
- buttons:
- - label: 'Cloudflare D1 pricing'
- to: 'https://developers.cloudflare.com/d1/platform/pricing/'
- columns:
- - key: 'title'
- label: ''
- - key: 'free'
- label: 'Free'
- - key: 'paid'
- label: 'Workers Paid ($5/month)'
- rows:
- - title: 'Databases'
- free: '10'
- paid: '50,000'
- - title: 'Rows read'
- free: '5 million / day'
- paid: '25 billion / month + $0.001/million'
- - title: 'Rows written'
- free: '100,000 / day'
- paid: '50 million / month + $1.00/million'
- - title: 'Storage'
- free: '5 GB'
- paid: '5 GB + $0.75/GB-month'
- - title: 'Max database size'
- free: '500 MB'
- paid: '10 GB (learn more)'
- - label: 'KV'
- icon: i-lucide-list
- buttons:
- - label: 'Cloudflare KV pricing'
- to: 'https://developers.cloudflare.com/kv/platform/pricing/'
- columns:
- - key: 'title'
- label: ''
- - key: 'free'
- label: 'Free'
- - key: 'paid'
- label: 'Workers Paid ($5/month)'
- rows:
- - title: 'Read'
- free: '100,000 / day'
- paid: '10 million / month + $0.50/million'
- - title: 'List'
- free: '1,000 / day'
- paid: '1 million / month + $5.00/million'
- - title: 'Write'
- free: '1,000 / day'
- paid: '1 million / month + $5.00/million'
- - title: 'Delete'
- free: '1,000 / day'
- paid: '1 million / month + $5.00/million'
- - title: 'Storage'
- free: '1 GB'
- paid: '1 GB + $0.50/GB-month'
- - label: 'Blob'
- icon: i-lucide-shapes
- buttons:
- - label: 'Cloudflare R2 pricing'
- to: 'https://developers.cloudflare.com/r2/pricing/'
- columns:
- - key: 'title'
- label: ''
- - key: 'free'
- label: 'Free Tier'
- - key: 'paid'
- label: 'Pricing'
- rows:
- - title: 'List + Write'
- free: '1 million / month'
- paid: '1 million / month + $4.50/million'
- - title: 'Read'
- free: '10 million / month'
- paid: '10 million / month + $0.36/million'
- - title: 'Storage'
- free: '10 GB / month'
- paid: '10 GB / month + $0.015/GB-month'
- - title: 'Egress'
- free: 'Unlimited'
- paid: 'Unlimited'
- - label: 'AI'
- icon: i-lucide-wand
- buttons:
- - label: 'Cloudflare AI pricing'
- to: 'https://developers.cloudflare.com/workers-ai/platform/pricing/'
- columns:
- - key: 'title'
- label: ''
- - key: 'free'
- label: 'Free'
- - key: 'paid'
- label: 'Workers Paid ($5/month)'
- rows:
- - title: 'Text Generation'
- free: '10,000 tokens / day'
- paid: '10,000 tokens / day + start at $0.10 / million tokens'
- - title: 'Embeddings'
- free: '10,000 tokens / day'
- paid: '10,000 tokens / day + start at $0.10 / million tokens'
- - title: 'Images'
- free: '250 steps (up to 1024x1024) / day'
- paid: '250 steps / day + start at $0.00125 per 25 steps'
- - title: 'Speech to Text'
- free: '10 minutes of audio / day'
- paid: '10 minutes of audio / day + $0.0039 per minute of audio input'
- - label: 'Vectorize'
- icon: i-lucide-move-3d
- buttons:
- - label: 'Cloudflare Vectorize pricing'
- to: 'https://developers.cloudflare.com/vectorize/platform/pricing/'
- columns:
- - key: 'title'
- label: ''
- - key: 'free'
- label: 'Free Tier'
- - key: 'paid'
- label: 'Pricing'
- rows:
- - title: 'Writes'
- free: 'Free'
- paid: 'Free'
- - title: 'Queried vector dimensions'
- free: '30 million / month'
- paid: '50 million / month + $0.01/million'
- - title: 'Stored vector dimensions'
- free: '5 million / month'
- paid: '10 million / month + $0.05/100 million'
button:
label: 'Discover Cloudflare pricing'
external: true
diff --git a/docs/content/_partials/pricing.yml b/docs/content/_partials/pricing.yml
new file mode 100644
index 00000000..368e5acc
--- /dev/null
+++ b/docs/content/_partials/pricing.yml
@@ -0,0 +1,157 @@
+plans:
+ - label: 'App'
+ icon: i-lucide-panels-top-left
+ buttons:
+ - label: 'Cloudflare Workers pricing'
+ to: 'https://developers.cloudflare.com/workers/platform/pricing/'
+ columns:
+ - key: 'title'
+ label: ''
+ - key: 'free'
+ label: 'Free'
+ - key: 'paid'
+ label: 'Workers Paid ($5/month)'
+ rows:
+ - title: 'Requests'
+ free: '100,000 / day'
+ paid: '10 million / month + $0.30/million'
+ - title: 'Worker size'
+ free: '3 MB'
+ paid: '10 MB'
+ - title: 'CPU time'
+ free: '10ms per invocation'
+ paid: '30s per invocation
30 million CPU ms + $0.02/million'
+ - title: 'CPU milliseconds'
+ free: '100'
+ paid: '30 million'
+ - title: 'Applications'
+ free: '100'
+ paid: '500'
+ - label: 'DB'
+ icon: i-lucide-database
+ buttons:
+ - label: 'Cloudflare D1 pricing'
+ to: 'https://developers.cloudflare.com/d1/platform/pricing/'
+ columns:
+ - key: 'title'
+ label: ''
+ - key: 'free'
+ label: 'Free'
+ - key: 'paid'
+ label: 'Workers Paid ($5/month)'
+ rows:
+ - title: 'Databases'
+ free: '10'
+ paid: '50,000'
+ - title: 'Rows read'
+ free: '5 million / day'
+ paid: '25 billion / month + $0.001/million'
+ - title: 'Rows written'
+ free: '100,000 / day'
+ paid: '50 million / month + $1.00/million'
+ - title: 'Storage'
+ free: '5 GB'
+ paid: '5 GB + $0.75/GB-month'
+ - title: 'Max database size'
+ free: '500 MB'
+ paid: '10 GB (learn more)'
+ - label: 'KV'
+ icon: i-lucide-list
+ buttons:
+ - label: 'Cloudflare KV pricing'
+ to: 'https://developers.cloudflare.com/kv/platform/pricing/'
+ columns:
+ - key: 'title'
+ label: ''
+ - key: 'free'
+ label: 'Free'
+ - key: 'paid'
+ label: 'Workers Paid ($5/month)'
+ rows:
+ - title: 'Read'
+ free: '100,000 / day'
+ paid: '10 million / month + $0.50/million'
+ - title: 'List'
+ free: '1,000 / day'
+ paid: '1 million / month + $5.00/million'
+ - title: 'Write'
+ free: '1,000 / day'
+ paid: '1 million / month + $5.00/million'
+ - title: 'Delete'
+ free: '1,000 / day'
+ paid: '1 million / month + $5.00/million'
+ - title: 'Storage'
+ free: '1 GB'
+ paid: '1 GB + $0.50/GB-month'
+ - label: 'Blob'
+ icon: i-lucide-shapes
+ buttons:
+ - label: 'Cloudflare R2 pricing'
+ to: 'https://developers.cloudflare.com/r2/pricing/'
+ columns:
+ - key: 'title'
+ label: ''
+ - key: 'free'
+ label: 'Free Tier'
+ - key: 'paid'
+ label: 'Pricing'
+ rows:
+ - title: 'List + Write'
+ free: '1 million / month'
+ paid: '1 million / month + $4.50/million'
+ - title: 'Read'
+ free: '10 million / month'
+ paid: '10 million / month + $0.36/million'
+ - title: 'Storage'
+ free: '10 GB / month'
+ paid: '10 GB / month + $0.015/GB-month'
+ - title: 'Egress'
+ free: 'Unlimited'
+ paid: 'Unlimited'
+ - label: 'AI'
+ icon: i-lucide-wand
+ buttons:
+ - label: 'Cloudflare AI pricing'
+ to: 'https://developers.cloudflare.com/workers-ai/platform/pricing/'
+ columns:
+ - key: 'title'
+ label: ''
+ - key: 'free'
+ label: 'Free'
+ - key: 'paid'
+ label: 'Workers Paid ($5/month)'
+ rows:
+ - title: 'Text Generation'
+ free: '10,000 tokens / day'
+ paid: '10,000 tokens / day + start at $0.10 / million tokens'
+ - title: 'Embeddings'
+ free: '10,000 tokens / day'
+ paid: '10,000 tokens / day + start at $0.10 / million tokens'
+ - title: 'Images'
+ free: '250 steps (up to 1024x1024) / day'
+ paid: '250 steps / day + start at $0.00125 per 25 steps'
+ - title: 'Speech to Text'
+ free: '10 minutes of audio / day'
+ paid: '10 minutes of audio / day + $0.0039 per minute of audio input'
+ - label: 'Vectorize'
+ icon: i-lucide-move-3d
+ buttons:
+ - label: 'Cloudflare Vectorize pricing'
+ to: 'https://developers.cloudflare.com/vectorize/platform/pricing/'
+ columns:
+ - key: 'title'
+ label: ''
+ - key: 'free'
+ label: 'Free Tier'
+ - key: 'paid'
+ label: 'Pricing'
+ rows:
+ - title: 'Writes'
+ free: 'Free'
+ paid: 'Free'
+ - title: 'Queried vector dimensions'
+ free: '30 million / month'
+ paid: '50 million / month + $0.01/million'
+ - title: 'Stored vector dimensions'
+ free: '5 million / month'
+ paid: '10 million / month + $0.05/100 million'
diff --git a/docs/pages/pricing.vue b/docs/pages/pricing.vue
index 16de3d0f..d13c26c8 100644
--- a/docs/pages/pricing.vue
+++ b/docs/pages/pricing.vue
@@ -118,39 +118,7 @@ onMounted(() => {
-
-
-
-
-
-
-
-
-
-
-
-
+