N
Nuxt7d ago
Alireza

SSR friendly usePreviousRoute()?

Say a visitor goes to a blog category page at /cat/tutorials and then clicks on a blog post going to /article/hello. The can also reach to /article/hello from other categories like /cat/videos. Now I want to create breadcrumbs that would show Posts > Tutorials > Hello or Posts > Videos > Hello providing the use navigation path. There will also be a default one if a user directly accesses the article page. How would you go about that? I was thinking maybe it is possible to access the from of the routeMiddleware?
<!-- the <Article> component -->
<script setup lang="ts">
definePageMeta({
middleware: [
function (to, from) {
// somehow make the from available to the component
},
'auth',
],
});

// then access the `from` here and build the breadcrumbs
</script>
<!-- the <Article> component -->
<script setup lang="ts">
definePageMeta({
middleware: [
function (to, from) {
// somehow make the from available to the component
},
'auth',
],
});

// then access the `from` here and build the breadcrumbs
</script>
13 Replies
kapa.ai
kapa.ai7d ago
Hi @Alireza. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- To implement the breadcrumbs functionality you described, you can't directly access the from route in a component as it's not persisted after navigation. However, you can achieve this using a combination of route middleware and state management. Here's an approach you can take: 1. Use route middleware to capture the from route:
// middleware/capturePreviousRoute.ts
export default defineNuxtRouteMiddleware((to, from) => {
const previousRoute = useState('previousRoute', () => null)
previousRoute.value = from.path
})
// middleware/capturePreviousRoute.ts
export default defineNuxtRouteMiddleware((to, from) => {
const previousRoute = useState('previousRoute', () => null)
previousRoute.value = from.path
})
2. Apply this middleware globally or to specific pages:
// nuxt.config.ts
export default defineNuxtConfig({
router: {
middleware: ['capturePreviousRoute']
}
})
// nuxt.config.ts
export default defineNuxtConfig({
router: {
middleware: ['capturePreviousRoute']
}
})
Or in your component:
definePageMeta({
middleware: ['capturePreviousRoute', 'auth']
})
definePageMeta({
middleware: ['capturePreviousRoute', 'auth']
})
3. In your Article component, you can now access the previous route:
<script setup>
const route = useRoute()
const previousRoute = useState('previousRoute')

const breadcrumbs = computed(() => {
const defaultBreadcrumbs = ['Posts', route.params.slug]

if (previousRoute.value && previousRoute.value.startsWith('/cat/')) {
const category = previousRoute.value.split('/')[2]
return ['Posts', category.charAt(0).toUpperCase() + category.slice(1), route.params.slug]
}

return defaultBreadcrumbs
})
</script>

<template>
<nav>
<ul>
<li v-for="(crumb, index) in breadcrumbs" :key="index">
{{ crumb }}
</li>
</ul>
</nav>
<!-- Rest of your article component -->
</template>
<script setup>
const route = useRoute()
const previousRoute = useState('previousRoute')

const breadcrumbs = computed(() => {
const defaultBreadcrumbs = ['Posts', route.params.slug]

if (previousRoute.value && previousRoute.value.startsWith('/cat/')) {
const category = previousRoute.value.split('/')[2]
return ['Posts', category.charAt(0).toUpperCase() + category.slice(1), route.params.slug]
}

return defaultBreadcrumbs
})
</script>

<template>
<nav>
<ul>
<li v-for="(crumb, index) in breadcrumbs" :key="index">
{{ crumb }}
</li>
</ul>
</nav>
<!-- Rest of your article component -->
</template>
This approach uses Nuxt's useState (https://nuxt.com/docs/api/composables/use-state) composable to persist the previous route across page navigations. The middleware captures the from route before each navigation, and the component can then use this information to build the breadcrumbs. Remember that this method will only work for internal navigations within your Nuxt app. If a user directly accesses the article page or comes from an external link, the previousRoute will be null, and you'll fall back to your default breadcrumbs. Nuxt Middleware Documentation (https://nuxt.com/docs/guide/directory-structure/middleware) provides more information on how to use middleware in Nuxt 3. Nuxt Composables Documentation (https://nuxt.com/docs/api/composables/use-state) explains how to use useState for cross-component and cross-page state management. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai7d ago
<@436210331925086218> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Alireza
AlirezaOP7d ago
@kapa.ai how about a Nuxt plugin and making the previousRoute globally available in the router?
kapa.ai
kapa.ai7d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To make the previousRoute globally available in the router using a Nuxt plugin, you can create a plugin that hooks into the router navigation and stores the previous route. This approach allows you to access the previous route information throughout your application. Here's how you can implement this: 1. Create a plugin file:
// plugins/router.client.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('app:created', () => {
const router = useRouter()
const previousRoute = ref(null)

router.beforeEach((to, from) => {
previousRoute.value = from
})

// Make previousRoute globally available
nuxtApp.provide('previousRoute', previousRoute)
})
})
// plugins/router.client.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('app:created', () => {
const router = useRouter()
const previousRoute = ref(null)

router.beforeEach((to, from) => {
previousRoute.value = from
})

// Make previousRoute globally available
nuxtApp.provide('previousRoute', previousRoute)
})
})
This plugin uses the app:created hook to ensure it runs after the Vue app is created. It then uses the router's beforeEach navigation guard to capture the from route before each navigation. 2. Access the previousRoute in your components: Now you can access the previousRoute in any component using the useNuxtApp() composable:
<script setup>
const nuxtApp = useNuxtApp()
const previousRoute = computed(() => nuxtApp.$previousRoute)

// Use previousRoute to build your breadcrumbs
const breadcrumbs = computed(() => {
const defaultBreadcrumbs = ['Posts', useRoute().params.slug]

if (previousRoute.value && previousRoute.value.path.startsWith('/cat/')) {
const category = previousRoute.value.path.split('/')[2]
return ['Posts', category.charAt(0).toUpperCase() + category.slice(1), useRoute().params.slug]
}

return defaultBreadcrumbs
})
</script>
<script setup>
const nuxtApp = useNuxtApp()
const previousRoute = computed(() => nuxtApp.$previousRoute)

// Use previousRoute to build your breadcrumbs
const breadcrumbs = computed(() => {
const defaultBreadcrumbs = ['Posts', useRoute().params.slug]

if (previousRoute.value && previousRoute.value.path.startsWith('/cat/')) {
const category = previousRoute.value.path.split('/')[2]
return ['Posts', category.charAt(0).toUpperCase() + category.slice(1), useRoute().params.slug]
}

return defaultBreadcrumbs
})
</script>
This approach has several advantages: 1. It's globally available, so you can access the previous route information from any component. 2. It updates automatically on each navigation. 3. It works for all internal navigations within your Nuxt app. Remember that for the initial page load or when coming from an external link, the previousRoute will be null, so you'll need to handle that case in your breadcrumb logic. This solution leverages Nuxt's plugin system and Vue's reactivity to provide a clean and efficient way to track and use the previous route globally in your application. Nuxt Plugins Documentation (https://nuxt.com/docs/guide/directory-structure/plugins) provides more information on how to use plugins in Nuxt 3. Nuxt App Documentation (https://nuxt.com/docs/guide/going-further/nuxt-app#providing-helpers) explains how to provide helpers globally using plugins. By using this plugin-based approach, you can easily access the previous route information throughout your application, making it simpler to implement features like dynamic breadcrumbs based on the user's navigation path. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai7d ago
<@436210331925086218> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Alireza
AlirezaOP7d ago
@kapa.ai Would that work on SSR?
kapa.ai
kapa.ai7d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To answer your question about whether the plugin-based approach for tracking the previous route would work with Server-Side Rendering (SSR), we need to consider some important aspects of Nuxt's rendering modes and client-side navigation. The plugin I suggested in the previous answer would not work as-is with SSR. Here's why: 1. The plugin was defined as router.client.ts, which means it only runs on the client-side. This is necessary because the Vue Router is not available during server-side rendering. 2. Even if we removed the .client suffix, the router.beforeEach guard would not be triggered during the initial server-side render, as there's no navigation happening on the server. 3. The previousRoute state would not be preserved between server and client, leading to hydration mismatches. However, we can modify our approach to work with SSR while still maintaining the functionality for client-side navigation. Here's how we can adapt it: 1. We can use a combination of a plugin and middleware to handle both SSR and client-side navigation. 2. For the initial SSR, we can use middleware to capture the referer header, which might give us information about the previous page. 3. For subsequent client-side navigations, we can use the plugin approach. Here's an example of how we could implement this:
// plugins/router.ts
export default defineNuxtPlugin((nuxtApp) => {
const previousRoute = useState('previousRoute', () => null)

if (process.client) {
const router = useRouter()
router.beforeEach((to, from) => {
previousRoute.value = from
})
}

// Make previousRoute globally available
nuxtApp.provide('previousRoute', previousRoute)
})
// middleware/capturePreviousRoute.ts
export default defineNuxtRouteMiddleware((to, from) => {
const previousRoute = useState('previousRoute', () => null)
const headers = useRequestHeaders(['referer'])

if (process.server && headers.referer) {
previousRoute.value = { path: headers.referer }
} else if (process.client) {
previousRoute.value = from
}
})
// plugins/router.ts
export default defineNuxtPlugin((nuxtApp) => {
const previousRoute = useState('previousRoute', () => null)

if (process.client) {
const router = useRouter()
router.beforeEach((to, from) => {
previousRoute.value = from
})
}

// Make previousRoute globally available
nuxtApp.provide('previousRoute', previousRoute)
})
// middleware/capturePreviousRoute.ts
export default defineNuxtRouteMiddleware((to, from) => {
const previousRoute = useState('previousRoute', () => null)
const headers = useRequestHeaders(['referer'])

if (process.server && headers.referer) {
previousRoute.value = { path: headers.referer }
} else if (process.client) {
previousRoute.value = from
}
})
In this setup: - The plugin sets up the client-side router guard and provides the previousRoute to the Nuxt app. - The middleware captures the referer header on the server-side and the from route on the client-side. - We use useState to ensure the state is shared between server and client, avoiding hydration mismatches. To use this in your components:
<script setup>
const route = useRoute()
const previousRoute = useState('previousRoute')

const breadcrumbs = computed(() => {
const defaultBreadcrumbs = ['Posts', route.params.slug]

if (previousRoute.value && previousRoute.value.path.includes('/cat/')) {
const category = previousRoute.value.path.split('/cat/')[1].split('/')[0]
return ['Posts', category.charAt(0).toUpperCase() + category.slice(1), route.params.slug]
}

return defaultBreadcrumbs
})
</script>
<script setup>
const route = useRoute()
const previousRoute = useState('previousRoute')

const breadcrumbs = computed(() => {
const defaultBreadcrumbs = ['Posts', route.params.slug]

if (previousRoute.value && previousRoute.value.path.includes('/cat/')) {
const category = previousRoute.value.path.split('/cat/')[1].split('/')[0]
return ['Posts', category.charAt(0).toUpperCase() + category.slice(1), route.params.slug]
}

return defaultBreadcrumbs
})
</script>
This approach should work with SSR, providing a best-effort solution for tracking the previous route. However, keep in mind: 1. The referer header might not always be available or accurate. 2. For the initial server render, you might not have previous route information unless the user came from another page within your site. Nuxt Rendering Modes Documentation (https://nuxt.com/docs/guide/concepts/rendering) provides more information about how Nuxt handles server-side and client-side rendering. Nuxt State Management Documentation (https://nuxt.com/docs/getting-started/state-management) explains how to use useState for state that works across both server and client. Remember to thoroughly test this solution in both development and production environments to ensure it behaves as expected with SSR. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai7d ago
<@436210331925086218> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Alireza
AlirezaOP7d ago
@kapa.ai Yeah, looks like useState is a better approach. The problem with the router is when: 1. user navigates from category to article 2. we have the previous route 3. the for some reason user reloads the page, and we lose the previous. So we better use something that keeps it as a state that is presisted somehow?
kapa.ai
kapa.ai7d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- You're right, using useState is indeed a better approach for maintaining state across page reloads. The router-based solution doesn't persist the previous route information when the user reloads the page, which is a limitation for your use case. To address this issue and persist the state even after a page reload, we can use a combination of useState and browser storage. Here's an improved approach: 1. We'll use useState to keep the previous route in memory. 2. We'll use localStorage (or sessionStorage) to persist the state across page reloads. 3. We'll use Nuxt's useStorage composable, which provides a convenient way to interact with different storage drivers, including localStorage. Here's how you can implement this:
// composables/usePreviousRoute.ts
export const usePreviousRoute = () => {
const previousRoute = useState('previousRoute', () => null)
const storage = useStorage()

const setPreviousRoute = (route) => {
previousRoute.value = route
storage.setItem('previousRoute', route)
}

const getPreviousRoute = async () => {
if (!previousRoute.value) {
previousRoute.value = await storage.getItem('previousRoute')
}
return previousRoute.value
}

return {
setPreviousRoute,
getPreviousRoute
}
}

// plugins/router.ts
export default defineNuxtPlugin((nuxtApp) => {
const router = useRouter()
const { setPreviousRoute } = usePreviousRoute()

router.beforeEach((to, from) => {
if (from.path !== to.path) {
setPreviousRoute(from)
}
})
})
// composables/usePreviousRoute.ts
export const usePreviousRoute = () => {
const previousRoute = useState('previousRoute', () => null)
const storage = useStorage()

const setPreviousRoute = (route) => {
previousRoute.value = route
storage.setItem('previousRoute', route)
}

const getPreviousRoute = async () => {
if (!previousRoute.value) {
previousRoute.value = await storage.getItem('previousRoute')
}
return previousRoute.value
}

return {
setPreviousRoute,
getPreviousRoute
}
}

// plugins/router.ts
export default defineNuxtPlugin((nuxtApp) => {
const router = useRouter()
const { setPreviousRoute } = usePreviousRoute()

router.beforeEach((to, from) => {
if (from.path !== to.path) {
setPreviousRoute(from)
}
})
})
Now, in your components, you can use this composable to access the previous route:
<script setup>
const route = useRoute()
const { getPreviousRoute } = usePreviousRoute()
const breadcrumbs = computed(async () => {
const defaultBreadcrumbs = ['Posts', route.params.slug]
const previousRoute = await getPreviousRoute()

if (previousRoute && previousRoute.path.startsWith('/cat/')) {
const category = previousRoute.path.split('/')[2]
return ['Posts', category.charAt(0).toUpperCase() + category.slice(1), route.params.slug]
}

return defaultBreadcrumbs
})
</script>
<script setup>
const route = useRoute()
const { getPreviousRoute } = usePreviousRoute()
const breadcrumbs = computed(async () => {
const defaultBreadcrumbs = ['Posts', route.params.slug]
const previousRoute = await getPreviousRoute()

if (previousRoute && previousRoute.path.startsWith('/cat/')) {
const category = previousRoute.path.split('/')[2]
return ['Posts', category.charAt(0).toUpperCase() + category.slice(1), route.params.slug]
}

return defaultBreadcrumbs
})
</script>
This approach has several advantages: 1. It persists the previous route even after page reloads. 2. It uses Nuxt's built-in storage capabilities, which are SSR-friendly. 3. It keeps the state in memory for quick access during the session. However, keep in mind: - This solution will not work for the very first visit to your site, as there won't be any previous route stored. - You might want to clear this stored route in certain scenarios, like when the user logs out. Nuxt State Management Documentation (https://nuxt.com/docs/getting-started/state-management) provides more information on using useState for state management. Nuxt Storage Documentation (https://nuxt.com/docs/api/composables/use-storage) explains how to use the useStorage composable for persistent storage. This solution should provide a robust way to maintain the previous route information across page reloads, allowing you to create more persistent and user-friendly breadcrumbs. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai7d ago
<@436210331925086218> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Alireza
AlirezaOP7d ago
I appreciate if anyone could share if they have experience with this.
manniL
manniL6d ago
I'd always build up the breadcrumbs based on the site structure, not necessarily through the user's click path but you can always save the click path / journey on client side (or use Vue Router's go/back function)
Want results from more Discord servers?
Add your server