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 :)
Related
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)
I am using Sanity.io as a backend, within Sanity I am handling the data for my main menu (parent/children object arrays).
Whilst I can fetch this data just fine, I want to do so via getStaticProps, as I'd like my site to remain entirely statically generated.
getStaticProps doesn't work in my layout.js file as it's not located in the "pages" directory. Nor does it work inside the _app.js file.
So currently Im having to fetch the menu within each page, and pass it as a prop into the Layout components (which has to be on every page now!), so that I can pass it from inside the Layout into the header component.
As such, my page looks like this:
function Page({ menu }) {
return (
<Layout menu={menu}>
<article>
...
</article>
</Layout>
)
}
export const getStaticProps = async (context) => {
const menu = await client.fetch(**menu query**);
return {
props: {
menu,
},
};
}
I am well aware that the Layout component should be situated inside the _app.js file instead, and that is where I'd rather it were, but I can't
This is obviously a very convoluted way of doing it, but I see no other method currently outside of using getInitialProps within Layout, but as I understand it, this will involved SSR rather than SSG.
I spent quite some time researching but the best answer I've found so far is that allowing getStaticProps in the layout "will be allowed in the near future", alas this was some time ago...
Can anybody point me in the right direction?
It seems crazy to have to include the layout on each page rather than have it on every page in the app, just so that I can get data into the header component at build time.
Although I still haven't found a native solution to this which doesn't involve fetching the menu data in getStaticProps on every page and passing it up the tree to the Layout -> Header.
I did come across a guide to a Node based solution from Brewhouse digital, NextJS getStaticProps With Components which is pretty cool.
Basically you can just create a "pre-build" process which is executed before the build, it fetches the data from your endpoint and stores it locally in a JSON file. You can then read that JSON file directly into your Layout or Header component for example.
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 have four projects.
First, Its called Portal and have some logical components.
And the others manage other things.
Each of them has its own routes, but I would like to create a higher o superior routes to nav between all of them.
How can create that path to access to each project I dont have the component??
For example,
I want in one path have:
/portal
/login
/home
/error
/project1
/project2
/project3
This will be in portal, but in portal I dont have a Component called roject1.
<Route exact path="/project1" component={Project1} />
How can create that path to access to each project I dont have the component??
Or I should create it and make a redirect to Project1??
Thanks
Yes, but you should create separate folders. It is generally bad practice to leave different projects in a single folder.
You can directly call files to be used with import { functionName } from './directory';
(further explanation can be found here)
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).