Add Redis or Valkey caching to a Lovable app

LovableValkeyRedisCachingDatabases

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?

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:

text
rediss://default:<password>@your-host.cloud.layerbase.dev:6379

The 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):

text
REDIS_URL=rediss://default:<password>@your-host.cloud.layerbase.dev:6379

Install a client. Both ioredis and redis work. I use ioredis in production because the reconnection behavior is more predictable:

bash
pnpm add ioredis

Create a single shared client. Putting this in lib/redis.ts is the convention:

ts
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:

ts
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:

ts
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:

ts
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.

ts
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:

bash
npm i -g spindb
spindb create lovable-cache-dev --engine valkey --start
spindb url lovable-cache-dev

Put the printed URL in .env.local:

text
REDIS_URL=redis://localhost:6379

spindb stop lovable-cache-dev shuts it down without removing the data. What is SpinDB? has the rest.

Wrapping up

The short version:

  1. Create a Valkey at layerbase.com/create/valkey
  2. Set REDIS_URL in Lovable's env settings
  3. pnpm add ioredis and create a shared client
  4. Use it for sessions, rate limits, and caching
  5. 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.

Something not working?