Problem
After running a Prisma migration that creates a new PostgreSQL role with NOLOGIN for Row-Level Security (RLS) purposes, the Neon-Vercel integration for preview deployments started providing connection URL environment variables (DATABASE_URL / POSTGRES_URL) that reference this new NOLOGIN role instead of the original default owner role.
This causes all preview deployment builds to fail at the prisma migrate deploy step with:
Error: P1001: Can’t reach database server at ep-XXXXX.us-east-1.aws.neon.tech:5432
The NOLOGIN role cannot be used to authenticate — it is designed to be assumed via SET LOCAL ROLE inside transactions, never for direct connections. The integration should not be selecting it as the connection role.
Expected Behavior
The integration should continue using the original default role (the Neon owner role) for connection URLs in preview branches, regardless of additional roles created via migrations. At minimum, a role with NOLOGIN should never be selected as the connection role.
Steps to Reproduce
- Have the Neon-Vercel integration set up with preview environments (separate Neon branch per Vercel preview deployment).
- Run a migration that creates a new PostgreSQL role with
NOLOGINand grants it table-level permissions:
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'tenant_user_role') THEN
CREATE ROLE tenant_user_role NOLOGIN;
END IF;
END $$;
GRANT USAGE ON SCHEMA public TO tenant_user_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO tenant_user_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO tenant_user_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO tenant_user_role;
- The same migration also enables RLS on tables and creates policies (policies apply to
{public}, i.e., all roles). - Push a new git branch to trigger a Vercel preview deployment.
- The Neon integration creates a new preview branch — but the connection URL environment variables now use
tenant_user_roleinstead ofdefault. - Build fails because
tenant_user_rolehasNOLOGINand cannot authenticate.
What I’ve Verified
- The injected env var is the problem — the connection URL provided by the integration contains the
tenant_user_role. Manually replacing it with the originaldefaultrole and password resolves the issue immediately. - Table and schema ownership is correct — all tables in
publicare owned bydefault, the schema is owned bydefault, and the database ACLs showdefaultas the owner:
SELECT tablename, tableowner FROM pg_tables WHERE schemaname = 'public';
-- All rows show: tableowner = "default"
SELECT nspname, pg_catalog.pg_get_userbyid(nspowner) AS owner
FROM pg_namespace WHERE nspname = 'public';
-- owner = "default"
- The role is correctly
NOLOGIN—SELECT rolname, rolcanlogin FROM pg_roles WHERE rolname = 'tenant_user_role'confirmsrolcanlogin = false. - The database is reachable — connecting via the Neon serverless driver (HTTP) with
SELECT 1succeeds. - Production deployments are unaffected — they use a separate Neon project.
- No role-level defaults were set — the migration does not alter
pg_db_role_setting.
Why the New Role Exists
The tenant_user_role is part of implementing Row-Level Security (RLS) for multi-tenant isolation. The application authenticates as the Neon owner role (default) and then assumes tenant_user_role via SET LOCAL ROLE within individual transactions. This is a standard PostgreSQL pattern.
Environment
- Framework: Next.js 15 (App Router)
- ORM: Prisma 6.8.0
- Database: Neon Serverless PostgreSQL
- Deployment: Vercel with Neon integration for preview environments
What I’ve Tried
- Manually overriding the connection URL with the default role and password — this works.
- Adding a keep-alive script using
@neondatabase/serverlessto ping the database via HTTP during builds. - Redeploying multiple branches — all new preview branches exhibit the same behavior.
Questions for the Neon Team
- Why does the integration select a
NOLOGINrole for the connection string? Shouldn’trolcanlogin = falsedisqualify a role? - Is there a way to explicitly configure which role the integration uses for preview branch connection URLs?
- Is this a known issue with the integration’s role detection logic when RLS is enabled?