diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c7244..25a83af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - **Faster favicon loading.** Site icons in the dashboard, referrers, and campaigns now use Next.js image optimization for better caching and lazy loading. - **Better page titles.** Browser tabs now show which site and page you're on (e.g. "Uptime ยท example.com | Pulse") instead of the same generic title everywhere. - **Link previews for public dashboards.** Sharing a public dashboard link on social media now shows a proper preview with the site name and description. +- **Faster login redirects.** If you're not signed in and try to open a dashboard or settings page, you're redirected to login immediately instead of seeing a blank page first. Already-signed-in users who visit the login page are sent straight to the dashboard. ## [0.10.0-alpha] - 2026-02-21 diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 0000000..1aa82b5 --- /dev/null +++ b/middleware.ts @@ -0,0 +1,65 @@ +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +const PUBLIC_ROUTES = new Set([ + '/login', + '/signup', + '/auth/callback', + '/pricing', + '/features', + '/about', + '/faq', + '/changelog', + '/installation', +]) + +const PUBLIC_PREFIXES = [ + '/share/', + '/integrations', + '/docs', +] + +function isPublicRoute(pathname: string): boolean { + if (PUBLIC_ROUTES.has(pathname)) return true + return PUBLIC_PREFIXES.some((prefix) => pathname.startsWith(prefix)) +} + +const AUTH_ONLY_ROUTES = new Set(['/login', '/signup']) + +export function middleware(request: NextRequest) { + const { pathname } = request.nextUrl + + const hasAccess = request.cookies.has('access_token') + const hasRefresh = request.cookies.has('refresh_token') + const hasSession = hasAccess || hasRefresh + + // * Authenticated user hitting /login or /signup โ†’ send them home + if (hasSession && AUTH_ONLY_ROUTES.has(pathname)) { + return NextResponse.redirect(new URL('/', request.url)) + } + + // * Public route โ†’ allow through + if (isPublicRoute(pathname)) { + return NextResponse.next() + } + + // * Protected route without a session โ†’ redirect to login + if (!hasSession) { + const loginUrl = new URL('/login', request.url) + return NextResponse.redirect(loginUrl) + } + + return NextResponse.next() +} + +export const config = { + matcher: [ + /* + * Match all routes except: + * - _next/static, _next/image (Next.js internals) + * - favicon.ico, manifest.json, icons, images (static assets) + * - api routes (handled by their own auth) + */ + '/((?!_next/static|_next/image|favicon\\.ico|manifest\\.json|.*\\.png$|.*\\.svg$|.*\\.ico$|api/).*)', + ], +}