Next.js: one page that match root '/' and dynamic route '/param' - javascript

I have a website as a single page that is using Next.js. I have the home page on route / that show a list of products. The code of this page is located in pages/index.js. Each product has an id so I can jump to it using /#product-id.
To make it more url friendly I replicate this behaviour using the product-id as a second param in the route like that: /product-id.
What I do is simply looking on the product-id param using useRouter():
const selectedProductId = useRouter().query['product-id']
And then scroll to the element with this id using js:
document.getElementById(selectedProductId ).scrollIntoView()
So I to change my script name from /pages/index.js to /pages/[product-id].js.
So now the route /1234 work has expected but if I go to / I get error 404.
So has someone an idea how I can match / and /param using one js file?

Optional catch all routes
Catch all routes can be made optional by including the parameter in double brackets ([[...slug]]).

Nextjs has file system based routing, so if you remove /pages/index.js of course you will get an 404 error. Also /pages/index.js and /pages/[product-id].js will render two separate pages.
To answer your question, if it is possible to match two routes like / and /[productId] in one file using nextjs I don't think that is possible however similar results can be achieved by using shallow routing specific to your use case.
So for your use case, I suggest using shallow routing unless you want to render the same component in both pages just to get the product-id or want to make use of hash URLs.
You can make product-id a query string parameter and update it using shallow-routing. Here is an example,
Keep /pages/index.js
import { useRouter } from 'next/router';
const router = useRouter()
// when want to change the productId call
router.push('/?productId=1234', undefined, { shallow: true })
// call the scrollToView inside a useEffect hook
useEffect(() => {
const productId = router.query.productId
// get the element using the productId above then call scrollIntoView()
})
// if using useEffect with the dependency router.query.productId,
// when you change the productId once and scroll up and try to change to the same -
// productId again it will not scroll to view, hence not using the dependency array
// at all
To explain more on what shallow routing does
Shallow routing will allow the change of URL without running the data fetching methods i.e getStaticProps or getServerSideProps again. Which will make the updated query and pathname available without changing the state. Read more about it nextjs docs.

Option 1: Extract the shared code
You could extract a Page component to a separate file and then import it in both /pages/index.js and /pages/[product-id].js, so the code is not duplicated.
Option 2: Use experimental rewrites feature
Assuming you have /pages/[product-id].js you can show this page when a user requests /.
You would need to add next.config.js.
module.exports = {
experimental: {
async rewrites() {
return [
{ source: "/", destination: "/[product-id]" },
];
}
}
}
So, when a user requests / they would see the content of /[product-id], just with the empty product id.
Note, that at the moment rewrite doesn't support auto-rendered dynamic pages, so you have to disable auto-rendering for the dynamic page.
You can do that by adding getServerSideProps to /pages/[product-id].js.
export async function getServerSideProps() {
return {
props: {},
}
}

Related

How to create dynamic components

I see opportunities to minimize network bytes transfer of my website, but can't come up with a proper solution.
In Gatsby, I make use of .mdx files. In these files I can use React Components, such as:
<Cards id="id_1" />
There are dozens of cards defined in a .json file, which can be used across the website by just calling this component in de mdx file and passing their id.
The Cards component looks like somewhat like this:
import React from 'react'
import Img from 'gatsby-image';
import { StaticQuery, graphql } from 'gatsby';
const Cards = (props) => {
const card_id = props.id ? props.id.slice(0, 2) : [] // grab id
return <StaticQuery
query={graphql`
query Query {
images: allFile(filter: { sourceInstanceName: { eq: "card-images" } }) {
edges {
node {
childImageSharp {
fluid(maxHeight: 256, quality: 100) {
...GatsbyImageSharpFluid_withWebp
...GatsbyImageSharpFluidLimitPresentationSize
}
}
name
}
}
}
allCardsJson {
nodes {
name
id
}
}
}
`}
render={(data) => {
return(
// returns a component by filtering 'data' by 'card_id'
)
}}
/>
}
Everything works fine, but...
When this component is used, the full result of StaticQuery (meaning: all cards since filtering is done inside the return, not inside the staticquery) is send to visitors of the page. This is unnecessary and a waste of network bandwidth, because for example only one card (or a few) is used on the page.
I understand that a StaticQuery is .. static. Thus, I cannot dynamically filter within this query to limit the size of the result.
DynamicQuerys are used when building pages, not inside components.
Is it possible to somehow create components with dynamic content (defined elsewhere), but limited to just the required data? (like by providing an id like I've tried)
I am thinking about creating a seperate file for each Card. Then import the Cards needed into the mdx file and passing it to the component. Thoughts?
There is no documentation about my use case. This makes me wonder if I'm using it as intended.
I solved it by adding the used data to the page context by editing the gatsby-node.js:
Get the mdxAST of the post, filter the components, then filter the ids used.
Add this list of ids to the page context.
Move the query stated in the question above to the post GraphQL query. Use the list of ids provided by page context to filter the data down to only the used ids in this post.
Pass this data as a property to the MDXRenderer.
Pass this data in the .mdx file to the cards component, such as: <Cards data={props.cards_data} />.
Now the compontent received the data without using a StaticQuery.
This works, but it feels kinda weird. There must be a better, more clean, solution in my opinion.
GraphQL queries are run ONCE when you run gatsby develop or gatsby build. This is a counterintuitive way how Gatsby works but read it yourself:
Gatsby uses GraphQL at build-time and not for live sites.
More information about the gatsby build process and how GraphQL factors into it.
This means you already built your component the way you are supposed to. During build all your cards are queried and kept in memory. When creating the HTML for your pages with your cards, only the cards with your ID are used to build your page. Thus the user only sees gets transmitted the pages with filtered IDs.
You can double check if really only the cards with the ID are inside your page:
run gatsby clean: make sure old fragments of your page are removed
run gatsby build: Create your site from scratch
Check your public folder in your project root. This is the classic webpage that gatsby builds. Navigate to your pages with your cards for example /public/blog/example-page-with-card. Take a look inside the HTML of the page: Does it contain all the cards or just the one with the IDs you need?

Get Target Path in Gatsby [duplicate]

This question already has answers here:
How to get previous url in react gatsby
(4 answers)
Closed 2 years ago.
Is there any way to find the path of the page that a user is on on a Gatsby site and store it in a const so that it can be checked next to the current path the user is on? In other words if they're on the /about page, and are clicking a link to navigate to the /contact page, I need to get the paths of both so that I can check them against each other. I know I can get location.pathname for the current url, but how do I find the path that they are navigating to when the click a link?
Since your code structure isn't clear. Assuming a simple anchor tag, you can do something like this:-
<a href="/new/link" onClick={getHref}>New Link</a>
And in your getHref method
function getHref(event){
console.log(event.target.href); // should log '/new/link'
}
Check if this works in your case.
Please forgive for any typo, I havent validated it.
Gatsby exposes props (because it extends from #reach/router from React) by default on the top-level components (this means pages). You can pass it to the child components as you wish or store it in a const or in a React's state.
Without knowing any page structure, I'll provide something dummy as an example:
import React from "react"
import { graphql } from "gatsby"
const YourPage = ({ location, data }) => {
console.log('your page is ', location.pathname)
return <div>Dummy content</div>
}
export default Page
export const query = graphql`
query PageQuery {
site {
siteMetadata {
siteURL
}
}
}
`
Your information is stored under props.location, that's why you can destructure it in the component's declaration.
In the case above, I've used pathname property but you have a bunch exposed. Check it out to find out which one fits your requirements.

How to create routes with dynamic params in gatsbyjs [duplicate]

I have setup gatsby project using this link. It is working correctly.
Now I know how to create route by defining the component inside the pages folder. But now I have a new challenge I need to create one dynamic route so that I can pass my id in it (Just like reactjs).
<Route path: "/path/:id"/>
How do I do that in gatsby?
You have to explicitly tell gatsby that a path should be dynamic. From the docs:
// gatsby-node.js
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/app/)) {
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}
and then you can use dynamic routing in src/pages/app.js
import { Router } from "#reach/router"
const SomeSubPage = props => {
return <div>Hi from SubPage with id: {props.id}</div>
}
const App = () => (
<Layout>
<Link to="/app/1">First item</Link>{" "}
<Link to="/app/2">Second item</Link>{" "}
<Router>
// ...dynamic routes here
<SomeSubPage path="/app/:id" />
</Router>
</Layout>
)
export default App
Everything that goes to /app/* will be handled dynamically now. You should find your id as usual in the props.
Have a look at their authentication example https://github.com/gatsbyjs/gatsby/tree/master/examples/simple-auth
You can use square brackets ([ ]) in the file path to mark any dynamic segments of the URL. For example, in order to edit a user, you might want a route like /user/:id to fetch the data for whatever id is passed into the URL.
src/pages/users/[id].js will generate a route like /users/:id
src/pages/users/[id]/group/[groupId].js will generate a route like /users/:id/group/:groupId
Reference: https://www.gatsbyjs.com/docs/reference/routing/file-system-route-api#creating-client-only-routes
You can use gatsby-plugin-create-client-paths. It uses matchPath. For more info check
https://www.gatsbyjs.org/docs/gatsby-internals-terminology/#matchpath
https://www.gatsbyjs.org/packages/gatsby-plugin-create-client-paths/
This answer is Super late, but for anyone in the future who is faced with this problem, I have a simpler solution.
In Gatsby terms it's called a Splat Route.
For examples, If you want some page "domain.com/profile/[id]", where id can be any number, which will be used to display different data inside the website, you should name your page as [...id].
Now inside the page you can access this id as
const ProfilePage = (props) => <div>This page is for id number {props.params.id}</div>
Note: Don't miss the 3 dots, that is what signifies a splat route in gatsby.

How to replace current url using react router

I need to change url completely using following way.
let mycomplteUrl = 'http://localhost/tracks/id/4/39'; // http://localhost/tracks/id/4/39/4 or
props.history.push(`${mycomplteUrl}`);
I'm dynamically creating this mycomplteUrl variable. sometimes variable can be something like
http://localhost/tracks/id/4/39 or http://localhost/tracks/id/4/39/4 or http://localhost/tracks/id/4/39/4/5 (dynamic) or any
it is the same only up to http://localhost/tracks/id/4 this part. I need to replace whole url just like window.location.href = mycomplteUrl in normal javascript, but using props.history.push because i need to avoid from page refresh
Note: for React Router v6
import { useNavigate } from 'react-router-dom';
function MyComponentOrHook() {
const navigate = useNavigate();
// push
navigate(url); // syntax
navigate("/about"); // example
// replace
navigate(url, { replace: true }); // syntax
navigate("/about", { replace: true }); // example
return ... // JSX or hook return values
}
Also, avoid using window.location if you're using React Router, except in rare cases (example - to navigate to an external link).
Reason:
The whole point of using a library React Router is to ease client-side routing so we don't have to do window.location....
window.location causes a reload, which should be avoided in React apps (or any SPA), mostly.
props.history.replace(mycomplteUrl);
If you want to avoid from a page refresh you can do it in this way
window.history.pushState({}, null, "/newPathname");
Try the below way and define the exact URL you want.
window.location.replace(`http://localhost:3000/${dynamic_value}`);

Passing Huge data between Vue js routes

I have an application which searches for flights using Vue.js and Vue Router.
I have two components, first one is search, which is on the base route '/'. When user clicks on search, it will send a request to server and gets a huge list of flights.
Then I need to call the result component on '/results' route and show the results using v-for.
I have two questions, first, how can I manually redirect to '/results' after I get the results.
Second and more important, what is the proper way of passing the results data to results component to use?
Inside your results components, you can put transition hooks in the route object. Read here: http://vuejs.github.io/vue-router/en/pipeline/hooks.html
The activate hook runs when a component is activated, and the component wont appear until the activate hook has run. Here's the example on that page, which would be similar to yours:
route:{
activate: function (transition) {
return messageService
.fetch(transition.to.params.messageId)
.then((message) => {
// set the data once it arrives.
// the component will not display until this
// is done.
this.message = message
})
}
}
So basically when they click search you send them to /results and this hook will handle loading the data in between.
Here's an advanced example of a mail app using vue-router that shows off a lot of the transition hooks in action: https://github.com/vuejs/vue-router/tree/dev/example/advanced

Categories