Skip to content

refactor(middleware): enhance subdomain handling and URL rewriting logic#66

Closed
Stivenjs wants to merge 2 commits into
mainfrom
templates-engine
Closed

refactor(middleware): enhance subdomain handling and URL rewriting logic#66
Stivenjs wants to merge 2 commits into
mainfrom
templates-engine

Conversation

@Stivenjs
Copy link
Copy Markdown
Contributor

@Stivenjs Stivenjs commented Jun 5, 2025

This commit updates the middleware to detect subdomains in both production and development environments. It implements URL rewriting to redirect users to the appropriate store page based on the detected subdomain, improving navigation and user experience. Additionally, the matcher configuration has been simplified to exclude specific paths, streamlining route handling.

Summary by CodeRabbit

  • New Features

    • Introduced dynamic store pages displaying the store name from the URL.
    • Enhanced subdomain routing to automatically redirect root requests on subdomains to corresponding store pages.
  • Improvements

    • Middleware now applies routing logic more broadly, excluding only API routes and static assets for smoother navigation.

This commit updates the middleware to detect subdomains in both production and development environments. It implements URL rewriting to redirect users to the appropriate store page based on the detected subdomain, improving navigation and user experience. Additionally, the matcher configuration has been simplified to exclude specific paths, streamlining route handling.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 5, 2025

Walkthrough

A new React client component was added to handle dynamic store routes, displaying the store name from the URL. The middleware logic was updated to dynamically detect subdomains, rewrite requests based on subdomain and environment, and expand its scope with a broader regex matcher, excluding certain static and API routes.

Changes

File(s) Change Summary
app/[store]/page.tsx Added a new client component StorePage that displays the store name from the dynamic route.
middleware.ts Enhanced middleware to detect subdomains, rewrite URLs accordingly, and updated matcher config.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Middleware
    participant StorePage

    User->>Middleware: Request to root or path with subdomain (e.g., tienda.fasttify.com)
    Middleware->>Middleware: Detect subdomain from hostname
    alt Path is root "/"
        Middleware->>Middleware: Rewrite path to /[subdomain]
    else Path does not start with subdomain
        Middleware->>Middleware: Rewrite path to /[subdomain]/[original path]
    end
    Middleware->>StorePage: Forward rewritten request
    StorePage->>User: Render store page with store name from URL
Loading

Poem

In the warren of code, a new path appears,
Subdomains detected—no need for cheers!
Store names now show with a hop and a skip,
Middleware rewrites with a clever little flip.
🐇✨ Onward we bound, with URLs in our grip!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc9b940 and 742105b.

📒 Files selected for processing (1)
  • middleware.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • middleware.ts
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Ejecutar pruebas unitarias
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Aplicar Prettier
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Comment thread middleware.ts Fixed
… sanitization

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
middleware.ts (1)

13-14: Extract environment configuration to a constant.

The environment check is inline, but it would be cleaner and more testable to extract this to a configuration object.

app/[store]/page.tsx (2)

1-1: Consider using a server component for better performance.

This component doesn't require client-side interactivity and could benefit from server-side rendering.

-'use client'
-
-import { useParams } from 'next/navigation'
+interface StorePageProps {
+  params: {
+    store: string
+  }
+}

-export default function StorePage() {
-  const params = useParams()
-  const store = params.store
+export default function StorePage({ params }: StorePageProps) {
+  const { store } = params

11-11: Avoid hard-coding text for internationalization support.

The text "Tienda:" is hard-coded in Spanish. Consider using a translation system for better internationalization support.

-      <h1>Tienda: {store}</h1>
+      <h1>Store: {store}</h1>

Alternatively, implement a proper i18n solution for multi-language support.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d59bda4 and bc9b940.

📒 Files selected for processing (2)
  • app/[store]/page.tsx (1 hunks)
  • middleware.ts (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Aplicar Prettier
  • GitHub Check: Ejecutar pruebas unitarias
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
middleware.ts (1)

82-82:

Details

❓ Verification inconclusive

Consider the security implications of the broad matcher pattern.

The new matcher pattern excludes only specific paths but applies middleware to all other routes. Ensure this doesn't inadvertently expose routes that should not have subdomain handling applied.


🏁 Script executed:

#!/bin/bash
# Description: Find all route files to verify they're compatible with subdomain handling

# Find all page.tsx and route.ts files
fd -e tsx -e ts '(page|route)\.(tsx?|js)$' app/

# Check for any API routes that might be affected
fd -e ts -e js 'route\.(ts|js)$' app/

Length of output: 1403


Re-evaluate matcher scope for subdomain middleware

Current pattern in middleware.ts:

matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],

This excludes only /api, /_next/static, /_next/image, and /favicon.ico, so it will run on every other route under the app directory. Confirm that this broad coverage is intended—pages like /login, /first-steps, your dynamic [slug] dashboards, etc., will all invoke the subdomain middleware.

  • Verify there are no legacy pages/api routes (or other endpoint handlers) that need to remain outside this middleware.
  • Check public/static asset endpoints (e.g., /robots.txt, /sitemap.xml) and add them to the exclusion list if they shouldn’t be intercepted.
  • If only store-specific or subdomain-bound routes should trigger the middleware, narrow the regex to target those paths explicitly.

Comment thread middleware.ts

export async function middleware(request: NextRequest) {
const path = request.nextUrl.pathname
const hostname = request.headers.get('host') || ''
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate the hostname to prevent header spoofing.

The hostname is retrieved directly from request headers without validation, which could be manipulated by clients. This poses a security risk as attackers could potentially bypass subdomain-based access controls.

Apply this diff to add hostname validation:

-  const hostname = request.headers.get('host') || ''
+  const hostname = request.headers.get('host') || ''
+  
+  // Validate hostname to prevent header spoofing
+  const allowedDomains = isProduction 
+    ? ['fasttify.com'] 
+    : ['localhost']
+  
+  const isValidHostname = allowedDomains.some(domain => 
+    hostname === domain || hostname.endsWith(`.${domain}`)
+  )
+  
+  if (!isValidHostname) {
+    return NextResponse.redirect(new URL('/', request.url))
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hostname = request.headers.get('host') || ''
const hostname = request.headers.get('host') || ''
// Validate hostname to prevent header spoofing
const allowedDomains = isProduction
? ['fasttify.com']
: ['localhost']
const isValidHostname = allowedDomains.some(domain =>
hostname === domain || hostname.endsWith(`.${domain}`)
)
if (!isValidHostname) {
return NextResponse.redirect(new URL('/', request.url))
}
🤖 Prompt for AI Agents
In middleware.ts at line 11, the hostname is obtained directly from request
headers without validation, which risks header spoofing. To fix this, validate
the hostname against a whitelist of allowed hostnames or use a robust parsing
method to ensure it matches expected patterns before using it for any access
control decisions. Reject or handle requests with invalid or unexpected
hostnames appropriately to prevent security bypass.

Comment thread middleware.ts
Comment on lines +31 to +44
// Si hay un subdominio y estamos en la raíz, reescribir a la ruta de la tienda
if (subdomain && subdomain !== 'www' && path === '/') {
// Reescribir la URL para mostrar la página de la tienda
const url = request.nextUrl.clone()
url.pathname = `/${subdomain}`
return NextResponse.rewrite(url)
}

// Si hay un subdominio y la ruta no empieza con el subdominio, agregar el prefijo
if (subdomain && subdomain !== 'www' && !path.startsWith(`/${subdomain}`)) {
const url = request.nextUrl.clone()
url.pathname = `/${subdomain}${path}`
return NextResponse.rewrite(url)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consolidate URL rewriting logic to eliminate duplication.

Both conditional blocks check for subdomain existence and perform similar URL rewriting. This can be simplified into a single block.

Apply this diff to consolidate the logic:

-  // Si hay un subdominio y estamos en la raíz, reescribir a la ruta de la tienda
-  if (subdomain && subdomain !== 'www' && path === '/') {
-    // Reescribir la URL para mostrar la página de la tienda
-    const url = request.nextUrl.clone()
-    url.pathname = `/${subdomain}`
-    return NextResponse.rewrite(url)
-  }
-
-  // Si hay un subdominio y la ruta no empieza con el subdominio, agregar el prefijo
-  if (subdomain && subdomain !== 'www' && !path.startsWith(`/${subdomain}`)) {
-    const url = request.nextUrl.clone()
-    url.pathname = `/${subdomain}${path}`
-    return NextResponse.rewrite(url)
-  }
+  // Reescribir URLs basadas en subdominios
+  if (subdomain && subdomain !== 'www') {
+    const url = request.nextUrl.clone()
+    
+    if (path === '/') {
+      // Si estamos en la raíz, reescribir a la ruta de la tienda
+      url.pathname = `/${subdomain}`
+    } else if (!path.startsWith(`/${subdomain}`)) {
+      // Si la ruta no empieza con el subdominio, agregar el prefijo
+      url.pathname = `/${subdomain}${path}`
+    } else {
+      // La ruta ya tiene el prefijo correcto
+      return NextResponse.next()
+    }
+    
+    return NextResponse.rewrite(url)
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Si hay un subdominio y estamos en la raíz, reescribir a la ruta de la tienda
if (subdomain && subdomain !== 'www' && path === '/') {
// Reescribir la URL para mostrar la página de la tienda
const url = request.nextUrl.clone()
url.pathname = `/${subdomain}`
return NextResponse.rewrite(url)
}
// Si hay un subdominio y la ruta no empieza con el subdominio, agregar el prefijo
if (subdomain && subdomain !== 'www' && !path.startsWith(`/${subdomain}`)) {
const url = request.nextUrl.clone()
url.pathname = `/${subdomain}${path}`
return NextResponse.rewrite(url)
}
// Reescribir URLs basadas en subdominios
if (subdomain && subdomain !== 'www') {
const url = request.nextUrl.clone()
if (path === '/') {
// Si estamos en la raíz, reescribir a la ruta de la tienda
url.pathname = `/${subdomain}`
} else if (!path.startsWith(`/${subdomain}`)) {
// Si la ruta no empieza con el subdominio, agregar el prefijo
url.pathname = `/${subdomain}${path}`
} else {
// La ruta ya tiene el prefijo correcto
return NextResponse.next()
}
return NextResponse.rewrite(url)
}
🤖 Prompt for AI Agents
In middleware.ts around lines 31 to 44, the URL rewriting logic for subdomains
is duplicated in two separate if blocks. Consolidate these by combining the
conditions that check for subdomain existence and rewrite the URL accordingly in
a single block. Use a single cloned URL object and adjust the pathname based on
whether the current path is '/' or does not start with the subdomain prefix,
then return the rewritten response once.

Comment thread middleware.ts
Comment on lines +16 to +29
// Detectar subdominios
let subdomain = ''
if (isProduction) {
// En producción: verificar si hay un subdominio (ej: tienda.fasttify.com)
const parts = hostname.split('.')
if (parts.length > 2 && hostname.endsWith('fasttify.com')) {
subdomain = parts[0]
}
} else {
// En desarrollo: usar el formato subdominio.localhost:3000 o localhost:3000
if (hostname.includes('.localhost')) {
subdomain = hostname.split('.')[0]
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor subdomain extraction logic for better maintainability.

The subdomain extraction logic has different branches for production and development, which could be simplified and made more robust.

Apply this diff to improve the subdomain extraction:

-  // Detectar subdominios
-  let subdomain = ''
-  if (isProduction) {
-    // En producción: verificar si hay un subdominio (ej: tienda.fasttify.com)
-    const parts = hostname.split('.')
-    if (parts.length > 2 && hostname.endsWith('fasttify.com')) {
-      subdomain = parts[0]
-    }
-  } else {
-    // En desarrollo: usar el formato subdominio.localhost:3000 o localhost:3000
-    if (hostname.includes('.localhost')) {
-      subdomain = hostname.split('.')[0]
-    }
-  }
+  // Detectar subdominios
+  const extractSubdomain = (hostname: string, isProduction: boolean): string => {
+    const cleanHostname = hostname.split(':')[0] // Remove port if present
+    const parts = cleanHostname.split('.')
+    
+    if (isProduction) {
+      // En producción: verificar si hay un subdominio (ej: tienda.fasttify.com)
+      if (parts.length > 2 && cleanHostname.endsWith('fasttify.com')) {
+        return parts[0]
+      }
+    } else {
+      // En desarrollo: usar el formato subdominio.localhost:3000
+      if (parts.length > 1 && cleanHostname.endsWith('localhost')) {
+        return parts[0]
+      }
+    }
+    
+    return ''
+  }
+  
+  const subdomain = extractSubdomain(hostname, isProduction)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Detectar subdominios
let subdomain = ''
if (isProduction) {
// En producción: verificar si hay un subdominio (ej: tienda.fasttify.com)
const parts = hostname.split('.')
if (parts.length > 2 && hostname.endsWith('fasttify.com')) {
subdomain = parts[0]
}
} else {
// En desarrollo: usar el formato subdominio.localhost:3000 o localhost:3000
if (hostname.includes('.localhost')) {
subdomain = hostname.split('.')[0]
}
}
// Detectar subdominios
const extractSubdomain = (hostname: string, isProduction: boolean): string => {
const cleanHostname = hostname.split(':')[0] // Remove port if present
const parts = cleanHostname.split('.')
if (isProduction) {
// En producción: verificar si hay un subdominio (ej: tienda.fasttify.com)
if (parts.length > 2 && cleanHostname.endsWith('fasttify.com')) {
return parts[0]
}
} else {
// En desarrollo: usar el formato subdominio.localhost:3000
if (parts.length > 1 && cleanHostname.endsWith('localhost')) {
return parts[0]
}
}
return ''
}
const subdomain = extractSubdomain(hostname, isProduction)
🤖 Prompt for AI Agents
In middleware.ts around lines 16 to 29, the subdomain extraction logic is split
between production and development environments, making it less maintainable.
Refactor this by consolidating the logic into a single, clear function or block
that handles both cases more robustly, ensuring it correctly extracts the
subdomain from hostnames like "subdomain.fasttify.com" in production and
"subdomain.localhost" in development, while simplifying the conditions and
improving readability.

Comment thread app/[store]/page.tsx
Comment on lines +5 to +14
export default function StorePage() {
const params = useParams()
const store = params.store

return (
<div>
<h1>Tienda: {store}</h1>
</div>
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add type safety and error handling for the store parameter.

The component lacks type checking and doesn't handle cases where the store parameter might be missing or invalid.

Apply this diff to add proper type safety and error handling:

 export default function StorePage() {
   const params = useParams()
-  const store = params.store
+  const store = params.store as string | undefined
+  
+  if (!store) {
+    return (
+      <div>
+        <h1>Error: Store not found</h1>
+      </div>
+    )
+  }

   return (
     <div>
       <h1>Tienda: {store}</h1>
     </div>
   )
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default function StorePage() {
const params = useParams()
const store = params.store
return (
<div>
<h1>Tienda: {store}</h1>
</div>
)
}
export default function StorePage() {
const params = useParams()
const store = params.store as string | undefined
if (!store) {
return (
<div>
<h1>Error: Store not found</h1>
</div>
)
}
return (
<div>
<h1>Tienda: {store}</h1>
</div>
)
}
🤖 Prompt for AI Agents
In app/[store]/page.tsx around lines 5 to 14, the StorePage component lacks type
safety for the params and does not handle cases where the store parameter is
missing or invalid. Add explicit typing for the params object, check if the
store parameter exists and is valid before rendering, and provide fallback UI or
error handling if the store parameter is absent or invalid to ensure robustness.

@Stivenjs Stivenjs closed this Jun 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants