Add Redis or Valkey caching to a Lovable app
Most Lovable apps hit the same wall around the time they pick up real users. Some query is slow. Some endpoint gets hit too often. You want sessions in something faster than Postgres. The answer is Redis (or Valkey, which is the same thing with a friendlier license). This post wires one into a Lovable app in under ten minutes.
Contents
- Redis or Valkey?
- Create the database
- Add it to Lovable
- Three patterns that pay for themselves
- Local development with SpinDB
Redis or Valkey?
If you do not know the difference, pick Valkey. The clients are identical (ioredis, redis, node-redis all work unchanged). The protocol is identical. The features through Redis 7.2 are identical. The only difference is the license.
In March 2024 Redis changed from open-source (BSD) to a dual SSPL/RSALv2 license. SSPL is not OSI-approved and the AWS, Google, and Linux Foundation reaction was to fork the last open-source version into Valkey. Valkey now ships on a BSD license and has all the security patches, performance work, and new features. If you are not actively using a feature that Redis Inc. introduced after the license change (RedisJSON, RediSearch as a hosted service), Valkey is the better default. Redis vs Valkey: which to use in 2026 has the longer take.
Layerbase Cloud offers both. The rest of this post uses Valkey because that is what I would pick today. The code is identical for Redis.
Create the database
Visit layerbase.com/create/valkey, name it (something like lovable-cache), and click Sign in and create. Provisioning is about ten seconds.
The dashboard gives you a connection string in the standard shape:
rediss://default:<password>@your-host.cloud.layerbase.dev:6379The rediss:// (two s) means TLS. Use it. The Layerbase Cloud listener requires TLS by default and refuses plain redis:// connections.
Add it to Lovable
In your Lovable project, set an environment variable. Name it REDIS_URL (or VALKEY_URL if you want to be specific):
REDIS_URL=rediss://default:<password>@your-host.cloud.layerbase.dev:6379Install a client. Both ioredis and redis work. I use ioredis in production because the reconnection behavior is more predictable:
pnpm add ioredisCreate a single shared client. Putting this in lib/redis.ts is the convention:
import Redis from 'ioredis'
export const redis = new Redis(process.env.REDIS_URL!, {
// The default of 10 retry attempts is too aggressive for serverless
// environments. Three is plenty.
maxRetriesPerRequest: 3,
})That is the entire setup. The rest is patterns.
Three patterns that pay for themselves
These are the three uses of Redis that earn back their cost in the first week of having it.
Session storage
If you are using NextAuth, Better-Auth, or any auth library that supports a Redis adapter, swap the database session table for Redis. Sessions get hit on every request, and serving them from Redis instead of Postgres takes a typical session lookup from 10-20ms to under 1ms.
For NextAuth:
import { UpstashRedisAdapter } from '@auth/upstash-redis-adapter'
import { Redis } from '@upstash/redis'
// The Upstash adapter accepts any Redis-compatible client.
// Pass an ioredis instance via the REST-compatible wrapper.If your auth library does not have a Redis adapter, write a thin wrapper. Sessions are just key-value pairs with a TTL, and Redis is the right shape for that:
import { redis } from '@/lib/redis'
export async function setSession(token: string, userId: string) {
// Sessions expire in 7 days.
await redis.set(`session:${token}`, userId, 'EX', 60 * 60 * 24 * 7)
}
export async function getSession(token: string): Promise<string | null> {
return await redis.get(`session:${token}`)
}
export async function deleteSession(token: string) {
await redis.del(`session:${token}`)
}Rate limiting
Anywhere users can hit an endpoint repeatedly (login, password reset, AI generation, payment retry), rate-limit it. Redis is the standard tool because the increment and check has to be atomic and cheap.
The simplest working version:
import { redis } from '@/lib/redis'
export async function rateLimit(
key: string,
limit: number,
windowSeconds: number,
): Promise<{ ok: boolean; remaining: number }> {
const current = await redis.incr(key)
if (current === 1) {
await redis.expire(key, windowSeconds)
}
return { ok: current <= limit, remaining: Math.max(0, limit - current) }
}
// In your API route:
const userIp = req.headers.get('x-forwarded-for') ?? 'unknown'
const { ok } = await rateLimit(`ai-generate:${userIp}`, 10, 60)
if (!ok) return new Response('Slow down', { status: 429 })10 AI generations per minute per IP. Adjust the numbers per endpoint.
Caching expensive queries
If you have a query that runs on every page load and only changes once an hour (a "trending posts" list, a "leaderboard," anything aggregate), cache it in Redis.
import { redis } from '@/lib/redis'
import { sql } from '@/lib/db'
export async function getTrendingPosts() {
const cached = await redis.get('trending:posts')
if (cached) return JSON.parse(cached)
const rows = await sql`
select id, title, score
from posts
where created_at > now() - interval '24 hours'
order by score desc
limit 20
`
// Cache for 5 minutes.
await redis.set('trending:posts', JSON.stringify(rows), 'EX', 300)
return rows
}The first request is slow, every subsequent request for five minutes is instant. That is usually enough.
Local development with SpinDB
You do not want to run app development against your production Valkey. SpinDB runs Valkey locally with one command:
npm i -g spindb
spindb create lovable-cache-dev --engine valkey --start
spindb url lovable-cache-devPut the printed URL in .env.local:
REDIS_URL=redis://localhost:6379spindb stop lovable-cache-dev shuts it down without removing the data. What is SpinDB? has the rest.
Wrapping up
The short version:
- Create a Valkey at layerbase.com/create/valkey
- Set
REDIS_URLin Lovable's env settings pnpm add ioredisand create a shared client- Use it for sessions, rate limits, and caching
- Local dev with SpinDB
The patterns above cover most of what Lovable apps need. If you also want a job queue (background tasks, scheduled jobs), see vqueue which is the Layerbase queue product running on top of Valkey.