diff --git a/README.md b/README.md
index 1c8a5e28..d9f1b6b7 100644
--- a/README.md
+++ b/README.md
@@ -77,7 +77,7 @@ _⚠️ READ BEFORE PROCEEDING_
- Generate an API key and store in environment variable `OPENAI_SECRET_KEY`
- Setup usage limits to avoid unexpected charges (_recommended_)
2. Add rate limiting (_recommended_)
- - As an additional precaution, create a [Vercel KV](https://vercel.com/docs/storage/vercel-kv/quickstart#create-a-kv-database) store and link it to your project in order to enable rate limiting—no further configuration necessary
+ - As an additional precaution, create an Upstash Redis store from the storage tab of the Vercel dashboard and link it to your project in order to enable rate limiting—no further configuration necessary
3. Configure auto-generated fields (optional)
- Set which text fields auto-generate when uploading a photo by storing a comma-separated list, e.g., `AI_TEXT_AUTO_GENERATED_FIELDS = title, semantic`
- Accepted values:
diff --git a/package.json b/package.json
index 66a658aa..7f5f452e 100644
--- a/package.json
+++ b/package.json
@@ -17,9 +17,9 @@
"@radix-ui/react-tooltip": "^1.1.8",
"@radix-ui/react-visually-hidden": "^1.1.2",
"@upstash/ratelimit": "^2.0.5",
+ "@upstash/redis": "^1.34.4",
"@vercel/analytics": "^1.5.0",
"@vercel/blob": "^0.27.1",
- "@vercel/kv": "^3.0.0",
"@vercel/speed-insights": "^1.2.0",
"ai": "^4.1.34",
"camelcase-keys": "^9.1.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 84632744..62e873dd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -31,16 +31,16 @@ importers:
version: 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@upstash/ratelimit':
specifier: ^2.0.5
- version: 2.0.5(@upstash/redis@1.34.3)
+ version: 2.0.5(@upstash/redis@1.34.4)
+ '@upstash/redis':
+ specifier: ^1.34.4
+ version: 1.34.4
'@vercel/analytics':
specifier: ^1.5.0
version: 1.5.0(next@15.1.7(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(svelte@4.2.17)(vue@3.4.27(typescript@5.7.3))
'@vercel/blob':
specifier: ^0.27.1
version: 0.27.1
- '@vercel/kv':
- specifier: ^3.0.0
- version: 3.0.0
'@vercel/speed-insights':
specifier: ^1.2.0
version: 1.2.0(next@15.1.7(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(svelte@4.2.17)(vue@3.4.27(typescript@5.7.3))
@@ -1763,11 +1763,8 @@ packages:
peerDependencies:
'@upstash/redis': ^1.34.3
- '@upstash/redis@1.31.3':
- resolution: {integrity: sha512-KtVgWBUEx/LGbR8oRwYexwzHh3s5DNqYW0bjkD+gjFZVOnREJITvK+hC4PjSSD+8D4qJ+Xbkfmy8ANADZ9EUFg==}
-
- '@upstash/redis@1.34.3':
- resolution: {integrity: sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ==}
+ '@upstash/redis@1.34.4':
+ resolution: {integrity: sha512-AZx2iD5s1Pu/KCrRA7KVCffu3NSoaYnNY7N9YI7aLAYhcJfsriQKTe+8OxQWJqGqFbrvm17Lyr9HFnDLvqNpfA==}
'@vercel/analytics@1.5.0':
resolution: {integrity: sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==}
@@ -1799,10 +1796,6 @@ packages:
resolution: {integrity: sha512-X5EG9W1cZW+Nbt/XdrJJSl5DzCXXn1JRP5nfFwkpFD03nB6uh6BldwX4syElHcciM4Pih8CS7Ri1mtLCJvxSHA==}
engines: {node: '>=16.14'}
- '@vercel/kv@3.0.0':
- resolution: {integrity: sha512-pKT8fRnfyYk2MgvyB6fn6ipJPCdfZwiKDdw7vB+HL50rjboEBHDVBEcnwfkEpVSp2AjNtoaOUH7zG+bVC/rvSg==}
- engines: {node: '>=14.6'}
-
'@vercel/speed-insights@1.2.0':
resolution: {integrity: sha512-y9GVzrUJ2xmgtQlzFP2KhVRoCglwfRQgjyfY607aU0hh0Un6d0OUyrJkjuAlsV18qR4zfoFPs/BiIj9YDS6Wzw==}
peerDependencies:
@@ -6500,18 +6493,14 @@ snapshots:
'@upstash/core-analytics@0.0.10':
dependencies:
- '@upstash/redis': 1.31.3
+ '@upstash/redis': 1.34.4
- '@upstash/ratelimit@2.0.5(@upstash/redis@1.34.3)':
+ '@upstash/ratelimit@2.0.5(@upstash/redis@1.34.4)':
dependencies:
'@upstash/core-analytics': 0.0.10
- '@upstash/redis': 1.34.3
+ '@upstash/redis': 1.34.4
- '@upstash/redis@1.31.3':
- dependencies:
- crypto-js: 4.2.0
-
- '@upstash/redis@1.34.3':
+ '@upstash/redis@1.34.4':
dependencies:
crypto-js: 4.2.0
@@ -6530,10 +6519,6 @@ snapshots:
throttleit: 2.1.0
undici: 5.28.4
- '@vercel/kv@3.0.0':
- dependencies:
- '@upstash/redis': 1.34.3
-
'@vercel/speed-insights@1.2.0(next@15.1.7(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(svelte@4.2.17)(vue@3.4.27(typescript@5.7.3))':
optionalDependencies:
next: 15.1.7(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
diff --git a/src/admin/AdminAppConfigurationClient.tsx b/src/admin/AdminAppConfigurationClient.tsx
index 1359f309..25b3d6ec 100644
--- a/src/admin/AdminAppConfigurationClient.tsx
+++ b/src/admin/AdminAppConfigurationClient.tsx
@@ -33,7 +33,7 @@ export default function AdminAppConfigurationClient({
hasDatabase,
isPostgresSslEnabled,
hasVercelPostgres,
- hasVercelKv,
+ hasRedisStorage,
hasStorageProvider,
hasVercelBlobStorage,
hasCloudflareR2Storage,
@@ -95,7 +95,7 @@ export default function AdminAppConfigurationClient({
// Connection status
databaseError,
storageError,
- kvError,
+ redisError,
aiError,
// Component props
simplifiedView,
@@ -358,25 +358,19 @@ export default function AdminAppConfigurationClient({
{renderEnvVars(['OPENAI_SECRET_KEY'])}
- {kvError && renderError({
- connection: { provider: 'Vercel KV', error: kvError},
+ {redisError && renderError({
+ connection: { provider: 'Redis', error: redisError},
})}
-
- Create Vercel KV store
-
- {' '}
- and connect to project in order to enable rate limiting
+ Create Upstash Redis store from storage tab
+ on Vercel dashboard and connect to this project
+ to enable rate limiting
const {
hasDatabase,
hasStorageProvider,
- hasVercelKv,
+ hasRedisStorage,
isAiTextGenerationEnabled,
} = APP_CONFIGURATION;
const [
databaseError,
storageError,
- kvError,
+ redisError,
aiError,
] = await Promise.all([
scanForError(hasDatabase, testDatabaseConnection),
scanForError(hasStorageProvider, testStorageConnection),
- scanForError(hasVercelKv, testKvConnection),
+ scanForError(hasRedisStorage, testRedisConnection),
scanForError(isAiTextGenerationEnabled, testOpenAiConnection),
]);
return {
databaseError,
storageError,
- kvError,
+ redisError,
aiError,
};
});
diff --git a/src/admin/insights/AdminAppInsightsClient.tsx b/src/admin/insights/AdminAppInsightsClient.tsx
index 7f613a9b..89c96264 100644
--- a/src/admin/insights/AdminAppInsightsClient.tsx
+++ b/src/admin/insights/AdminAppInsightsClient.tsx
@@ -234,8 +234,9 @@ export default function AdminAppInsightsClient({
'yellow',
)}
expandContent={<>
- Create Vercel KV store and link to this project
- in order prevent abuse by to enabling rate limiting.
+ Create Upstash Redis store from storage tab on
+ Vercel dashboard and link to this project to
+ prevent abuse by enabling rate limiting.
>}
/>}
{(noStaticOptimization || debug) && kv.get('test');
diff --git a/src/platforms/openai.ts b/src/platforms/openai.ts
index 87016aa2..949b8dd3 100644
--- a/src/platforms/openai.ts
+++ b/src/platforms/openai.ts
@@ -1,12 +1,17 @@
import { generateText, streamText } from 'ai';
import { createStreamableValue } from 'ai/rsc';
import { createOpenAI } from '@ai-sdk/openai';
-import { kv } from '@vercel/kv';
+import { Redis } from '@upstash/redis';
import { Ratelimit } from '@upstash/ratelimit';
-import { AI_TEXT_GENERATION_ENABLED, HAS_VERCEL_KV } from '@/app-core/config';
+import {
+ AI_TEXT_GENERATION_ENABLED,
+ HAS_REDIS_STORAGE,
+} from '@/app-core/config';
import { removeBase64Prefix } from '@/utility/image';
import { cleanUpAiTextResponse } from '@/photo/ai';
+const redis = Redis.fromEnv();
+
const RATE_LIMIT_IDENTIFIER = 'openai-image-query';
const RATE_LIMIT_MAX_QUERIES_PER_HOUR = 100;
const MODEL = 'gpt-4o';
@@ -15,9 +20,9 @@ const openai = AI_TEXT_GENERATION_ENABLED
? createOpenAI({ apiKey: process.env.OPENAI_SECRET_KEY })
: undefined;
-const ratelimit = HAS_VERCEL_KV
+const ratelimit = HAS_REDIS_STORAGE
? new Ratelimit({
- redis: kv,
+ redis,
limiter: Ratelimit.slidingWindow(RATE_LIMIT_MAX_QUERIES_PER_HOUR, '1h'),
})
: undefined;
diff --git a/src/platforms/redis.ts b/src/platforms/redis.ts
new file mode 100644
index 00000000..e0512be1
--- /dev/null
+++ b/src/platforms/redis.ts
@@ -0,0 +1,5 @@
+import { Redis } from '@upstash/redis';
+
+const redis = Redis.fromEnv();
+
+export const testRedisConnection = () => redis.get('test');