ZyVOP Logo
Content That Connects
SeriesAI NewsLeaderboardWrite for Us
ZyVOP Logo
Content That Connects

Empowering developers and creators with cutting-edge insights, comprehensive tutorials, and innovative solutions for the digital future.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • API Documentation
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

© 2026 ZyVOP. Crafted with care for the developer community.

Made with ❤️ by the ZyVOP team
All systems operational
HomeTutorialVue.js vs Next.js: Modal Routing — A Side-by-Side Breakdown
Tutorial

Vue.js vs Next.js: Modal Routing — A Side-by-Side Breakdown

Heba Allah Hashim
Heba Allah HashimSoftware Engineer
August 14, 2026
3 min read
Vue.js vs Next.js: Modal Routing — A Side-by-Side Breakdown
#webdev#Next.js#Vue.js
👍2

Let's look at the breakdown of both frameworks side-by-side to understand the massive shift in mindset.


Project 1: The Vue.js Approach (Code-Driven Logic)

Step 1: Initialize the Project

npm create vite@latest vue-modal-app -- --template vue
cd vue-modal-app
npm install vue-router@4

Step 2: Configure the Routing Paths (src/router.js)

import { createRouter, createWebHistory } from 'vue-router'
import GalleryView from './views/GalleryView.vue'

export const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: GalleryView,
      children: [
        {
          path: 'photo/:id',
          component: () => import('./components/PhotoModal.vue'),
          props: true
        }
      ]
    }
  ]
})

Step 3: Link the Router to the Application

Update src/main.js:

import { createApp } from 'vue'
import App from './App.vue'
import { router } from './router'

createApp(App).use(router).mount('#app')

Replace src/App.vue:

<template>
  <router-view />
</template>

Step 4: Create the Gallery List Page (src/views/GalleryView.vue)

<template>
  <div style="padding: 20px; font-family: sans-serif;">
    <h1>Vue Photo Gallery</h1>
    
    <div style="display: flex; gap: 20px;">
      <div 
        v-for="photoId in ['sunset', 'ocean', 'mountains']" 
        :key="photoId"
        @click="openPhoto(photoId)"
        style="width: 150px; height: 100px; background: #3b82f6; color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; border-radius: 8px;"
      >
        View {{ photoId }}
      </div>
    </div>

    <!-- Renders our Child Popup component over the grid -->
    <router-view v-if="showModal" @close="closeModal" />
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()
const showModal = ref(false)

// Crucial: The Watcher forces the modal open if an ID exists on hard refresh
watch(
  () => route.params.id,
  (newId) => {
    showModal.value = !!newId
  },
  { immediate: true }
)

function openPhoto(id) {
  router.push(`/photo/${id}`)
}

function closeModal() {
  router.push('/')
}
</script>

Step 5: Create the Popup Modal Component (src/components/PhotoModal.vue)

<template>
  <div style="position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 9999;">
    <div style="background: white; padding: 30px; border-radius: 12px; text-align: center; width: 300px;">
      <h2>Vue Modal Layout</h2>
      <p style="font-size: 24px; font-weight: bold; text-transform: uppercase; color: #3b82f6;">
        🌌 {{ id }}
      </p>
      <button @click="$emit('close')" style="margin-top: 20px; padding: 8px 16px; cursor: pointer;">Close Overlay</button>
    </div>
  </div>
</template>

<script setup>
defineProps({ id: String })
defineEmits(['close'])
</script>

Run via npm run dev to view.


Project 2: The Next.js Approach (Folder-Driven Architecture)

Step 1: Initialize the Project

# Configuration flags: TypeScript (Yes), ESLint (No), src/ (No), App Router (Yes)
npx create-next-app@latest next-modal-app
cd next-modal-app

Step 2: Create the File Folders

Manually create the exact folders matching the Hierarchy Map above directly inside your editor's file sidebar to prevent system escape errors.

Step 3: Set up the Main Gallery Page (app/gallery/page.tsx)

import Link from 'next/link';

export default function GalleryPage() {
  return (
    <div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
      <h1>Next.js Photo Gallery</h1>
      
      <div style={{ display: 'flex', gap: '20px' }}>
        {['sunset', 'ocean', 'mountains'].map((photoId) => (
          <Link 
            key={photoId} 
            href={`/gallery/photo/${photoId}`}
            style={{ 
              width: '150px', 
              height: '100px', 
              background: '#ef4444', 
              color: 'white', 
              display: 'flex', 
              alignItems: 'center', 
              justifyContent: 'center',
              textDecoration: 'none', 
              borderRadius: '8px' 
            }}
          >
            View {photoId}
          </Link>
        ))}
      </div>
    </div>
  );
}

Step 4: Configure the Layout Slots (app/gallery/layout.tsx)

export default function GalleryLayout({
  children,
  modal
}: {
  children: React.ReactNode;
  modal: React.ReactNode;
}) {
  return (
    <div>
      {children}
      {modal}
    </div>
  );
}

Step 5: Create the Slot Fallback (app/gallery/@modal/default.tsx)

export default function DefaultModal() {
  // Fixes the unmounted slot mismatch crash by returning a baseline null
  return null;
}

Step 6: Create the Intercepted Popup View (app/gallery/@modal/(.)photo/[id]/page.tsx)

'use client';
import { useRouter, useParams } from 'next/navigation';

export default function InterceptedPhotoPopup() {
  const router = useRouter();
  const params = useParams(); 

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999 }}>
      <div style={{ background: 'white', padding: '30px', borderRadius: '12px', textAlign: 'center', width: '300px' }}>
        <h2>Next.js Intercepted Popup</h2>
        <p style={{ fontSize: '24px', fontWeight: 'bold', textTransform: 'uppercase', color: '#ef4444' }}>
          📸 {params.id}
        </p>
        <button onClick={() => router.back()} style={{ marginTop: '20px', padding: '8px 16px', cursor: 'pointer' }}>
          Close Overlay
        </button>
      </div>
    </div>
  );
}

Step 7: Create the Standalone Fallback Page (app/gallery/photo/[id]/page.tsx)

import Link from 'next/link';

interface PageProps {
  params: Promise<{ id: string }>;
}

export default async function StandalonePhotoPage({ params }: PageProps) {
  // Modern Next.js rule: params must be unwrapped asynchronously
  const { id } = await params;

  return (
    <div style={{ padding: '40px', fontFamily: 'sans-serif', textAlign: 'center' }}>
      <h1>Standalone Full-Screen View</h1>
      <div style={{ margin: '40px auto', width: '400px', height: '250px', background: '#ef4444', color: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '32px', borderRadius: '12px' }}>
        🌌 {id.toUpperCase()}
      </div>
      <Link href="/gallery" style={{ color: '#ef4444' }}>← Back to Gallery List</Link>
    </div>
  );
}

Run via npm run dev and navigate to http://localhost:3000/gallery.

Heba Allah Hashim

Heba Allah Hashim

Software Engineer

Full-Stack Developer with 3+ years of experience building production web applications. I work across the stack — Python/FastAPI/Django on the backend and Vue.js/Next.js/TypeScript on the frontend.

Comments (0)

Login to post a comment.

Related Posts

The SEO Strategy Behind ZyVOP: Why Cross-Posting Outward Beats Importing Inward

Most developer platforms treat content syndication as a two-way street. It isn't. Every piece of content you cross-post has a canonical URL, and that URL determines which domain gets the SEO credit. Here's the strategy behind ZyVOP's approach — and the bug that almost broke it.

Read article

Building MindWell: Accessible Mental Health Tech for Developers

MindWell is an accessible mental health platform designed to bridge developer wellness with tech innovation. Learn how human-centered UI design, accessible features, and AI-driven tools were combined to create an empowering wellness space for software engineers and creators.

Read article

How ZyVOP's Syndication Engine Works: Dev.to, Hashnode, Medium, and Bluesky

Cross-posting sounds simple until you try to build it. Every platform has different APIs, different formats, different quirks, and different failure modes. Here's the complete technical breakdown of how ZyVOP's syndication engine works under the hood.

Read article

The Real Cost of Running a Developer Platform

Most "cost of running my SaaS" posts hide the free tiers and skip the disasters. Here's every line item behind ZyVOP — including the Vercel timeout that was one slow API response away from silently killing cross-posts.

Read article

What the Instagram API doesn't tell you about publishing

The Instagram API answers OK and nothing appears on your profile. Six gotchas that cost me an afternoon each: the two step publish, the URL Meta fetches itself, the video wait, carousel limits, and the token that dies quietly.

Read article