#Redirects and middleware
1 messages · Page 1 of 1 (latest)
The issue you're experiencing—where static assets like CSS are not loading when using middleware to redirect subdomains—is a common pitfall. This happens because your middleware is also intercepting requests for static assets (such as those under /_next/), and redirecting or rewriting them, which leads to 404 errors for those resources.
Solution:
You need to update your middleware to skip processing for static assets and API routes. This way, requests for CSS, JS, images, and other static files are not redirected or rewritten, and will load correctly.
Here's how you can do it in your middleware.ts:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Skip middleware for static files and API routes
if (
request.nextUrl.pathname.startsWith('/_next') ||
request.nextUrl.pathname.startsWith('/api') ||
request.nextUrl.pathname.startsWith('/images')
) {
return NextResponse.next();
}
// Example: Redirect all requests to the admin subdomain to /admin
const host = request.headers.get('host') || '';
if (host.startsWith('admin.')) {
return NextResponse.rewrite(new URL('/admin', request.url));
}
return NextResponse.next();
}
This pattern is confirmed as a working solution by users who faced the same issue: by checking the pathname and skipping the middleware for static assets, your CSS and other static files will load as expected, while your subdomain logic will still work for page requests GitHub Issue: middleware rewrite and tailwind.css.
Key points:
- Always skip middleware for paths like
/_next,/api,/images, and any other static or public asset routes. - This ensures that only actual page requests are handled by your subdomain logic, and static assets are served normally.
Let me know if you need help with your next.config.ts as well!
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@spring canopy Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@frozen crane So I don't even need a next.config.ts if i use this middleware?
Let me check the knowledge base for you again. Give me one moment! :books:
You are correct: if your redirect/rewrite logic is fully handled in middleware.ts, you do not need to define the same logic in next.config.ts. Middleware allows you to programmatically handle redirects, rewrites, and other routing logic before the request is completed, and it runs after any redirects or rewrites defined in next.config.js/ts Middleware documentation.
However, you might still use next.config.ts for other unrelated configuration (e.g., custom webpack config, environment variables, etc.), but for your described use case—redirecting subdomains and skipping static assets—middleware alone is sufficient.
Good to know:
- If you have a large, static list of redirects or rewrites that never change, you can use
next.config.tsfor those.- If your logic is dynamic or based on request headers, cookies, or other runtime data, middleware is the recommended approach NextResponse.redirect in Middleware.
So, for your scenario, you do not need a next.config.ts for routing if you are handling everything in middleware.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@spring canopy Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@frozen crane /** @type {import('next').NextConfig} /
const nextConfig = {
async headers() {
return [
{
source: '/api/:path',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
],
},
]
},
}
module.exports = nextConfig
enough for the next config or anmything else
Let me check the knowledge base for you again. Give me one moment! :books:
Your next.config.js (or .ts) example sets some important security headers for /api/:path* routes:
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
],
},
]
},
}
module.exports = nextConfig
However, there are a few things to consider:
-
Recommended Security Headers:
According to the official Next.js and Vercel documentation, it is recommended to set the following headers for robust security:Content-Security-PolicyStrict-Transport-SecurityX-Frame-OptionsX-Content-Type-OptionsReferrer-Policy
See: Vercel Conformance Security Headers, Next.js Security Headers Options
-
X-XSS-ProtectionHeader:
The knowledge sources do not mentionX-XSS-Protectionas a recommended header. Modern browsers have deprecated this header in favor of Content Security Policy (CSP). -
Scope:
Your config only applies these headers to/api/:path*. If you want these headers (or others) to apply to all routes, you should adjust thesourcepattern accordingly (e.g.,/(.*)). -
Example of a more complete configuration:
const securityHeaders = [
{ key: 'Content-Security-Policy', value: "default-src 'self'" },
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
];
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
];
},
};
See: Next.js Security Headers Options, Vercel Conformance Security Headers
Summary:
Your config is a good start, but for best practices and to pass security conformance checks, you should add the recommended headers above and consider applying them to all routes, not just /api/:path*. The knowledge sources do not mention X-XSS-Protection as necessary.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@spring canopy Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@frozen crane import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
if (
request.nextUrl.pathname.startsWith('/_next') ||
request.nextUrl.pathname.startsWith('/api') ||
request.nextUrl.pathname.startsWith('/images')
) {
return NextResponse.next();
}
const host = request.headers.get('host') || '';
if (host.startsWith('admin.')) {
return NextResponse.rewrite(new URL('/admin', request.url));
}
return NextResponse.next();
}
using this as middleware, when I go to admin.website.com, instead ofshowing me website.com/admin's page it just shows me website.com's page
and to access the admin panel i have to https://admin.website.com/admin/{page}
Let me check the knowledge base for you again. Give me one moment! :books:
The behavior you're seeing is because NextResponse.rewrite(new URL('/admin', request.url)) rewrites all requests to the admin subdomain to /admin, but it does not preserve the original path. This means that when you visit <https://admin.website.com,> you get website.com/admin, but if you visit <https://admin.website.com/page,> it still rewrites to website.com/admin, not website.com/admin/page.
If you want all routes under the admin subdomain to map to /admin plus the original path, you need to append the original pathname after /admin. For example, <https://admin.website.com/foo> should rewrite to <https://website.com/admin/foo.>
Here's how you can adjust your middleware:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
if (
request.nextUrl.pathname.startsWith('/_next') ||
request.nextUrl.pathname.startsWith('/api') ||
request.nextUrl.pathname.startsWith('/images')
) {
return NextResponse.next();
}
const host = request.headers.get('host') || '';
if (host.startsWith('admin.')) {
// Prepend /admin to the original path, unless it's already there
let newPath = request.nextUrl.pathname;
if (!newPath.startsWith('/admin')) {
newPath = '/admin' + newPath;
}
// Preserve search params
if (request.nextUrl.search) {
newPath += request.nextUrl.search;
}
return NextResponse.rewrite(new URL(newPath, request.url));
}
return NextResponse.next();
}
This way:
<https://admin.website.com> →/admin<https://admin.website.com/foo> →/admin/foo<https://admin.website.com/admin/bar> →/admin/bar(no double/admin/admin)
This approach is consistent with how subdomain-based multi-tenant routing is handled in Next.js middleware, as seen in the official multi-tenant example, where the middleware rewrites the path based on the subdomain and preserves the original path and query parameters Next.js Multi-Tenant Example.
If you need more advanced subdomain handling, you can refer to the official multi-tenant starter kit for Next.js, which demonstrates dynamic subdomain routing using middleware Next.js Multi-Tenant Example.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@spring canopy Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@frozen crane but i dont want it to add /admin
I just want it to show the /admin page in the subdomain without adding it
Let me check the knowledge base for you again. Give me one moment! :books:
If you want the admin subdomain (e.g., admin.website.com) to show the /admin page without adding /admin to the URL (so that admin.website.com displays the same content as website.com/admin), you should use a rewrite that maps the root of the subdomain to /admin, but does not change the visible URL.
You can achieve this using a rewrite rule in your next.config.js with the has field to match the host, as described in the official documentation:
module.exports = {
async rewrites() {
return [
{
source: '/:path*',
has: [
{
type: 'host',
value: 'admin.website.com',
},
],
destination: '/admin/:path*',
},
]
},
}
This configuration will:
- Rewrite all requests to
admin.website.comto/admin/:path*on your main site, - But the URL in the browser will remain as
admin.website.com.
This approach is recommended for subdomain-to-subpath routing and is documented in the official Vercel and Next.js guides Can I redirect from a subdomain to a subpath?, Header, Cookie, and Query Matching.
You do not need to use middleware for this use case.
Middleware is only necessary if you need dynamic or conditional logic that cannot be handled by static rewrites.
Summary:
- Use a rewrite in
next.config.jswith thehasfield for the host. - This will show the
/adminpage atadmin.website.comwithout changing the URL to/admin.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@spring canopy Kapa.ai is still learning and improving, please let me know how I did by reacting below.
this will also rewrite static assets? how do i make them show up
@frozen crane this will also rewrite static assets? how do i make them show up
Let me check the knowledge base for you again. Give me one moment! :books:
Great question! According to the official Next.js documentation, when you use rewrites in next.config.js with the has field for subdomain routing, static assets (such as those in /public or /_next/static) are not rewritten by default. This is because of the order in which Next.js processes routes:
headersare checked/appliedredirectsare checked/appliedbeforeFilesrewrites are checked/applied- Static files from the public directory,
_next/staticfiles, and non-dynamic pages are checked/served afterFilesrewrites are checked/appliedfallbackrewrites are checked/applied
This means static assets are served before rewrites are applied, so your CSS, JS, and images will load correctly as long as they exist in the appropriate directories (see official docs, see also).
If you notice static assets are not loading:
- Double-check that your static files are in the
/publicdirectory or are being generated by Next.js in the correct location. - Make sure your rewrite rule does not unintentionally match asset paths like
/_next/staticor/public.
Summary:
You do not need to add special handling for static assets when using rewrites as described—they will be served correctly by default, and your admin subdomain will display the /admin page with all assets loading as expected.
If you need to be extra cautious, you can use the beforeFiles phase for your rewrite, which ensures static files are checked first (see docs).
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@spring canopy Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@frozen crane /** @type {import('next').NextConfig} /
const nextConfig = {
async headers() {
return [
{
source: '/api/:path',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
],
},
]
},
async redirects() {
return [
{
source: '/admin',
destination: 'https://admin.personaessential.com',
permanent: true, // Or false if it's a temporary redirect
},
{
source: '/admin/:path*',
destination: 'https://admin.personaessential.com/:path*',
permanent: true, // Or false if it's a temporary redirect
},
]
},
async rewrites() {
return [
{
source: '/:path*',
destination: '/:path*', // Default rewrite for personaessential.com
},
{
// This condition ensures this rewrite rule only applies to admin.personaessential.com
has: [
{
type: 'host',
value: 'admin.personaessential.com',
},
],
source: '/:path*',
destination: '/admin/:path*', // Rewrites admin.personaessential.com to /admin
},
]
},
}
module.exports = nextConfig
will this rewrite static abnd api requests?
Let me check the knowledge base for you again. Give me one moment! :books:
Based on the official Next.js documentation, rewrites in next.config.js are applied after static files and API routes are checked/served. This means that your rewrite rule for admin.personaessential.com:
{
has: [
{ type: 'host', value: 'admin.personaessential.com' },
],
source: '/:path*',
destination: '/admin/:path*',
}
will not rewrite requests for static assets or API routes. Static files (such as those in /public or /_next/static) and API routes (/api/:path*) are served before rewrites are applied. This is explicitly stated in the documentation:
static files from the public directory,
_next/staticfiles, and non-dynamic pages are checked/served before rewrites are applied
Next.js rewrites order
So, your static assets and API routes will not be affected by this rewrite rule, and they will continue to work as expected.
If you want to be absolutely certain, you can use the beforeFiles phase for your rewrite, which is also applied after headers/redirects but before static files, ensuring the same behavior see docs.
Summary:
- Your rewrite rule for the admin subdomain will not rewrite static asset or API requests.
- Static files and API routes are served before rewrites are applied, according to the official Next.js routing order.