Before rendering a page for a given route, I'd like to synchronously fetch the necessary data first. Ideally, I'd like to have the data fetching within the page component, but I'm not opposed to doing it in the router files. I've read and tried various ways of doing it, but part of the challenge comes from the fact that there are also multiple ways of building components and the usage of certain features vary.
In my case, I'm building single file components using the Composition API and <script setup> syntax. The Vue Router documentation link talks about "fetching before navigation" in which I could reach for beforeRouteEnter or beforeRouteUpdate, but this is shown using the Options API. They do have the page for the Composition API mentioning I could use onBeforeRouteUpdate, but that uses the setup() function. I figured I'd try it out anyway with <script setup>:
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
onBeforeRouteUpdate(() => {
console.log('onBeforeRouteUpdate')
})
</script>
However, this does not execute. The closest method I've tried that works is fetching the data in the router, using the beforeEnter guard, and setting the data onto the meta property, which can then get accessed on the route instance in the component:
beforeEnter: (to, from, next) => {
fetch('https://pokeapi.co/api/v2/pokemon/ditto')
.then(res => res.json())
.then(res => {
to.meta.pokemon = res;
next();
});
}
But with this, which is noted in the documentation, beforeEnter only triggers when entering the route. Params changes will not retrigger this, meaning that I'd have to set up a watcher on the route in the component anyway. I might as well just have had all this logic in the component itself.
I just can't seem to find a good way to do this, but I might have overlooked something. If anyone has some pointers or advice, I'd appreciate it. Thanks in advance.
There is a solution - using top level await - https://vuejs.org/api/sfc-script-setup.html#top-level-await
Just wrap your RouterView component in a Suspense component like shown here - https://vuejs.org/guide/built-ins/suspense.html#combining-with-other-components (don't use the components you don't need)
The only caveat is that the 'loading screen' will be visible on the initial request.
I made a little demo for you so you can try it out - https://github.com/ileue/vue-top-level-await-demo
First off, beforeRouteUpdate is only triggered when updating the actual route but not going to another component/page as officially told here.
An example on what could trigger that lifecycle hook would be
<button #click="$router.push({ hash: `#${Math.floor(Math.random() * 10)}` })">
random hash
</button>
onBeforeRouteLeave perfectly works tho, as you can expect, when moving from page to page.
As for the initial question, you could implement some kind of router middleware like Nuxt does it. That way, you could await an HTTP call and only then allow for an actual navigation. Hence creating a block navigation effect pretty much.
I'm not sure on how to write that with Composition API, but I know that it perfectly works with Options API (quite some blog posts available). setup by itself behaving in it's own life-cycly way, I guess quite some things are rather tricky.
TLDR: a good ol' router middleware + wrapper around your pages (like a layout) is the perfect combo in your case IMO. There, you could set a single watcher for quite a lot of pages at the same time.
But everything depends on how you want to organize yourself and structure your code of course.
Skeleton screens bring a sense of being faster than something blocking but overall, you could also use prefetch (coming with Nuxt too by default) to get some hints and potentially load some assets even before they are needed. (+ other tricks in the same domain to speed up your network requests)
Related
I've recently started working on a frontend project that uses Next.js, and I'm trying to figure out how to best take advantage of the SSR it provides. I want to use some client-side fetching as well, and while the documentation explains that using React hooks in a component will make it render client-side, it seems quite sparse about how this affects the other components up/down the DOM tree.
I tried to test the SSR/CSR behaviour by creating a site with some components with/without React hooks in them, and opening it in Chrome first with JavaScript enabled, then disabled. So far I've found out a couple of things and I was wondering if my assumptions are right:
it seems that components that use hooks work like typical React components - their children re-render when effect/state hooks in the parent render, so they don't seem to benefit from SSR and should instead be optimised using React features like React.memo.
when I open the site with JS disabled, it seems that all components come pre-rendered - components that use state and display it, even show the initial state set in the hook. For example, the below component:
export const TestComponent = () => {
const [num, setNum] = useState(13)
return (
<div>
<button onClick={() => setNum(num+1)}>CLICK</button>
<h2>Number: {num}</h2>
</div>
)
}
actually contains the text "Number: 13" (the button obviously doesn't work without JS though)
What I'm also wondering is how much using global context providers is going to diminish performance improvement from SSR. Let's say my _app.jsx wraps each page in a provider that periodically queries an API. Does it completely void the advantage presented by SSR, seeing as it will probably cause the entire page to re-render?
Think of SSR as first paint data, and what is the trigger for it. Anything that causes the trigger for SSR to run, you will be getting data from getServerSideProps
SSR is usually done for SEO purposes so the bot can crawl the pre-rendered data. Any data that is depended on client-side fetch will be less prone for crawling.
Let's say for a shopping page, the initial products load can be SSR while subsequent products on clicking of a 'Load More' button happens on client side with the useSWR hook. This is a valid approach, and it mixes both SSR and CSR.
If a provider queries the API, that is a client side fetch. It won't coincide with what SSR is doing. It's important to know the trigger for SSR, which usually happens on a first visit, reload and anything that triggers a route change / push. Both SSR and client-side fetch do things their own way.
Further Reads:
https://swr.vercel.app/docs/with-nextjs#pre-rendering-with-default-data
https://nextjs.org/docs/routing/shallow-routing
There is no one-size-fits-all answer to this question, as the best way to take advantage of Next.js server-side rendering while using React hooks extensively will vary depending on your specific needs and preferences. However, some tips on how to achieve this include:
1. Try using the Next.js client and server libraries together. The Next.js client library allows you to use Next.js features in your React applications, while the Next.js server library allows you to use Next.js to rendering your applications on the server. This can be a great way to take advantage of both Next.js' server-side rendering capabilities and React's hooks feature.
2. Use react-router-dom for routing. react-router-dom is a popular routing library for React that also supports server-side rendering. This can be a good choice for applications that need to use Next.js' server-side rendering capabilities as well as router functionality.
Here is the small example. I hope it would help you.
import React from 'react';
import { useRouter } from 'next/router';
const Page = () => {
const router = useRouter();
const { id } = router.query;
return <p>Post: {id}</p>;
};
export default Page;
I am trying to make a simple form that slides to the right like this one used by DigitalOcean when you click on "Sign up using email": https://cloud.digitalocean.com/registrations/new.
The transition itself is pretty easy, what caught my attention is that they use 2 separate routes for this, the first one under /new and the other one under /email. These 2 seem to be separate pages and not just 2 different states to which a route is programmatically added, how can I do this in NextJS?
I believe the feature that you're looking for is shallow routing.
From the Docs:
Shallow routing allows you to change the URL without running data
fetching methods again, that includes getServerSideProps,
getStaticProps, and getInitialProps.
You'll receive the updated pathname and the query via the router
object (added by useRouter or withRouter), without losing state.
Note, however, that:
[s]hallow routing only works for URL changes in the current page.
See also:
next/router - router.push()
Dynamic Routes
This answer by #metaaa may also shed some light re implementation.
Best of luck and happy coding!
I am currently updating a React project to use Next.js. I've stumbled on a slight issue with Dynamic Routing which so far I can't find any solutions online.
I have a number of info pages that use the same component and display different content based on the slug. Using react-router these can be specified as follows:
path: /:infoPage(about-us|terms|privacy|contact|faqs)
/about-us, /terms, /privacy, /contact, /faqs
So far for Next.js, I thought I'd use a dynamic route as seen below. The only issue is that the URLs will now have /infoPage/___ - /infoPage/about-us, /infoPage/terms.
/pages
/infoPage
/[infoPage].js
As a solution links can be updated using as with the proper URL:
<Link href="/infoPage/[infoPage]?infoPage=about-us" as="/about-us">
<a>About Us</a>
</Link>
Whilst this works when clicking on a link, refreshing the page will end up a 404 page - since Next.js is not aware of any page as /about-us.
Possible Solutions
Use dynamic route /pages/[infoPages].js. Not quite sure if this is the ideal solution as it would most probably act like a fallback to any other page.
I suppose I could have every page declared separately within /pages and import the same component in each page but it will be slightly repetitive.
/pages
/about-us.js
/terms.js
...
Keep /infoPage/[infoPage].js route, add redirects from client/server to the proper URL
I may be over thinking this, the second solution would not be the end of the world but I'm still wondering if there's a better solution.
Thanks in advance :)
In case someone is looking for an answer to this question, here are two solutions you may consider.
1. Dynamic Route in /pages at root level
Use Case: Pages using the same components with similar data (for example API call to get page content)
If you have certain pages similar to each other at root level, you can create a dynamic route such as below:
/pages
/[rootPage].js
Keep in mind that if these pages are using Static Site Generation, slugs need to declared using getStaticPaths.
Further details on getStaticPaths here
It is also important to note that predefined routes take precedence over dynamic routes. Should a dynamic route have a slug the same as a predefined route, Next will always render the predefined route. So this approach can be risky at root level.
Dynamic Routing Caveats
2. Separate routes, one getStaticProps function
Use Case: Pages require similar data/API calls but use different components
You may opt to have routes for each page but use the same getStaticProps function.
Routes:
/pages
/about-us.js
/terms.js
getStaticProps helper function:
const getPageStaticProps = () => {
return async (context) => {
// generic logic here
}
}
Page (about-us.js, terms.js):
// /pages/about-us.js
function AboutPage(props) {
return <AboutComponent {...props} />
}
export const getStaticProps = getPageStaticProps()
---
// /pages/terms.js
function TermsPage(props) {
return <TermsComponent {...props} />
}
export const getStaticProps = getPageStaticProps()
This can also be implemented using getServerSideProps.
I opted for the second solution as it felt safer and still benefitted from having code shared between different pages. Obviously there may be other solutions our there that can be used :)
I've been reading a lot of articles and the docs (some great resources I found for ex: https://coursetro.com/posts/code/144/A-Vuex-Tutorial-by-Example---Learn-Vue-State-Management ), all of that to understand how vuex works ( when/why should I use actions and not mutations, etc), I got it, so my example is the following:
I'm using router, with one route now, which uses component Home
My Home component looks like this (nothing special):
<template>
<div class="home">
<!-- Components to inject goes here -->
</div>
</template>
<script>
export default {
name: 'home'
}
</script>
I have a backend, to which I have to make some API calls, that together with user actions, have to control which components to inject/eject into the UI
That said, I don't want them already declared to show/hide, I want to control whether they are inside that div or not, depending on actions in my Vuex store.
Why? Because I want to be able to track them as I track mutations done by actions with Vue DevTools, this would allow me to have complete control of my app, ex:
MUTATION1
ADD_COMPONENT_1
MUTATION2...
REMOVE_COMPONENT_1
Please, correct me if I need to clarify myself, is there a way to accomplish it? (any link, source is highly appreciated)
Without more information it's difficult to fully understand your intentions. However, I guess you will find 'Dynamic Components' useful: https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
This will allow you to render components dynamically depending on the state.
If you want to change the whole page or big parts of it, then you probably need vue-router and a <router-view>
I'm implementing multi-language support in my app, and I guess this is the last thing that I would need in order to be able to change between languages without reloading the whole app/page. (I already have a solution with full page reload.)
For a simple example let's say this is how my router looks:
Router.map(function() {
this.route('search', { path: t('search') });
this.route('item', { path: `${t('item')}/:id`);
});
The t function would be getting the correct translation for the given strings in the currently active language.
The structure of the route hierarchy won't change, the only things that need to be updated are the path strings. Application state should be kept, of course.
I'm wondering whether this is possible to do.
I am not %100 sure about the correctness of what I wrote but Router.map is executed and resources with the definitions given within this method is transformed to a DSL instance and that is then passed to the actual router maintained by Ember.Router itself. In order to achieve what you want I believe what we need is dynamic modification to the router even if it is just the paths you need yo modify not the whole route structure.
If you look at the following github issue, Ember.js no more supports dynamically adding routes (hence no dynamic modification to the existing ones I believe). With all that said, I believe what you want is not possible without reloading the whole app (hence losing the application state).