Dynamic route segment in nextJS static js build - reactjs

is it possible to use dynamic routing path with nextJS, in a static build context?
Assuming my build is a set of html, js, css assets which could be hosted on any web server (Apache, Nginx, S3, Netlify etc...).
For instance I've got a path defined /pages/[article].js/read and I'd like to be able to use the structure: /page/articleA/read where articleA is a dynamic variable.
If that's feasible, how to achieve this?

Yes, it is possible to use dynamic routes when using static-site-generation with nextjs. You have to use data-fetching method getStaticProps to fetch the data needed based on the dynamic route param. Additionally you have to use another function getStaticPaths to generate a list of paths for which nextjs will build static pages at build time. For example,
Suppose for page /pages/articles/[articleId].js, Here is what pseudo code might look like for you.
// you have to generate and return a list of paths
export const getStaticPaths = async () => {
const articles = await /*Fetch the articles from backend or make a db query*/
const paths = articles.map(article => ({ params: { articleId: article.id }}));
return {
paths,
fallback: false
}
}
export const getStaticProps = async (ctx) => {
const articleId = ctx.params.articleId;
// fetch the data using the article id and return as props
return {
props: /* fetched data */
}
}
// create the page component and export it as the default export
You can read more about it in the docs. Keep in mind because the fallback is set to false nextjs will show a 404 page for any path that is not returned from the function getStaticPaths, You can read about fallback here.

Related

How I can get localstorage data inside getServerSideProps

I am using NextJS 12. I am trying to get local storage object. When I use localstorage inside getServerSideProps I get an error like this ReferenceError: localStorage is not defined. I tried to use it outside the function as well but I still get this error. Is there any way to use it inside getServerSideProps.
export async function getServerSideProps({ query }) {
const id = query.id;
const getData = JSON.parse(localStorage.getItem("form"));
console.log(getData)
return {
props: {},
}
Welcome to StackOverflow, as it refers in the documentation
If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps.
Localstorage is only available on the client side and you are trying to access it in a server side only function , you can use something like
if (typeof window !== 'undefined') {
// your code
const id = query.id;
const getData = JSON.parse(localStorage.getItem("form"));
console.log(getData)
}
Please review this article to get more information on running client side only code.
Another approach would be to use a dynamic import where the hello3 component would contain the code accessing local storage.
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
Another way would be using cookies instead of using localstorage, I had the same problem when I developed my last application and I solved it using the nookies package
Nookies: A collection of cookie helpers for Next.js

Fetching Data from API using NextJS and Material UI React

I am trying to create dynamic pages that shows individual book details (i.e. title/author etc) on a separate page based on a query string of the "id" of each book. However, I am having difficulty in understanding how to make a request to a API endpoint using NextJS that will get the book details based on its "id". I would like to use Material UI as a UI Framework.
ISSUE: When I run npm run dev the book page loads but the book's "props" are not being passed along to the BookAttributes component. The console.log(book) I added in the book page is undefined and the console.log(title) in BookAttributes is undefined as well.
I've tested the API endpoint in POSTMAN and it appears to work.
When I refactor the same code using Semantic UI-React instead of Material UI, the book pages load correctly.
I am using the NextJS Material UI starter template from the Material UI website as a baseline.
I am fairly new to NextJS and Material UI so your assistance and guidance would be greatly appreciated. Thank you for your help on this!
Here is the code I have so. I have tried to keep in clean and simple.
BOOK PAGE (within 'pages' directory)
import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';
function Book({ book }) {
console.log(book)
return (
<>
<h1>Book Page</h1>
<BookAttributes {...book} />
</>
)
}
Book.getInitalProps = async ({ query: { _id } }) => {
const url = 'http://localhost:3000/api/book';
const payload = { params: { _id }}
const response = await axios.get(url, payload)
return { book: response.data }
}
export default Book;
BOOK API ENDPOINT (within 'pages/api' directory)
import Book from '../../models/Book';
import connectDb from '../../utils/connectDb';
connectDb()
export default async (req, res) => {
const { _id } = req.query
const book = await Book.findOne({ _id })
res.status(200).json(book);
}
BOOK ATTRIBUTE COMPONENT (within 'components' directory)
import React from 'react';
function BookAttributes({ title }) {
console.log(title)
return (
<>
<h1>{title}</h1>
</>
)
}
export default BookAttributes;
You should be using dynamic routes here if you want to work with data-fetching methods like getStaticProps or getServerSideProps.
You can create a page like pages/book/[id].js. But to generate the page you have to decide what data-fetching method you want to run. If the data for the page doesn't change very often you can choose to use static-site-generation using getStaticProps which will generate the pages at build time. If the data will be changing a lot you can either do server-side-rendering using getServerSideProps or fetch the data client-side.
Here is an example for your use-case that you can use for server-side-rendering using getServerSideProps, keep in mind the API call inside getServerSideProps might fail so you should have appropriate error handling.
In pages/book/[id].js
import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';
export const getServerSideProps = async (ctx) => {
const bookId = ctx.params?.id
const url = 'http://localhost:3000/api/book';
const response = await axios.get(url, { params: { _id: bookId} })
return {
props: {
book: response.data
}
}
}
function Book({ book }) {
return (
<>
<h1>Book Page</h1>
<BookAttributes {...book} />
</>
)
}
export default Book;
Using static-site-generation
Because the page is dynamic you have to provide a list of paths for which nextjs will generate the pages. You can do that by exporting an async function called getStaticPaths.
in pages/book/[id].js
import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';
export const getStaticPaths = async () => {
// here you have two options if you know all the ids of the books
// you can fetch that data from the api and use all the ids to generate
// a list of paths or show a fallback version of page if you don't know all
// ids and still want the page to be static
// Pseudo code might look like this
const res = await axios.get('api-endpoint-to-fetch-all-the-books')
const paths = res.data.map(book => ({ params: { id: book.id }}))
return {
paths,
fallback: false
}
}
export const getStaticProps = async (ctx) => {
const bookId = ctx.params?.id
const url = 'http://localhost:3000/api/book';
const response = await axios.get(url, { params: { _id: bookId} })
return {
props: {
book: response.data
}
}
}
function Book({ book }) {
return (
<>
<h1>Book Page</h1>
<BookAttributes {...book} />
</>
)
}
export default Book;
The fallback property in the returned value of getStaticPaths is somewhat important to understand. If you know all the necessary id for the pages you can set the fallback to false. In this case nextjs will simply show a 404 error page for all the paths that were not returned from the function getStaticPaths.
If fallback is set to true nextjs will show a fallback version of page instead of a 404 page for the paths that were not returned from the getStaticPaths function. Now where should you set fallback to true? Let's suppose in your case new books are added to the database frequently, but the data for the books doesn't change very often so you want the pages to be static. In this case, you can set fallback to true and generate a list of paths based on avaliable book ids. For the new books nextjs will first show the fallback version of the page than fetch the data based on the id provided in the request and will send the data as JSON which will be used to render the page in the client.

React async data fetch does not fetch data on static build

I'm building a static site using Sanity.io and Gatsby.js.
The data is hosted through Sanity and I'm fetching it via GROQ.
Host and build is on Netlify.
The problem is that when I build I only get a rendered result of Loading. It does work in development. (which, I'm sure is because of hotloading)
How would I build a render a (async) data fetch for a build? I don't want to have this in a componentDidMount() or useEffect() Because from what I understand then the query would be client side. that would result in an API request for every page load, right? I just want one on build instead.
Can I pause the build/render just as long as it has no data yet?
Below is my simplified code
import React from "react"
import useSWR from "swr"
import client from "../../../../../sanityClient"
import BlockCarousel from "../blockCarousel/blockCarousel"
const BlockCarouselBlog = ({ block }) => {
const skip = 0
const limit = block.itemAmount ? block.itemAmount : 10
const params = { skip, limit: skip + limit - 1 }
let {
data,
error,
} = useSWR(
'*[_type == "blog"] | order(_createdAt asc) [$skip..$limit]',
query => client.fetch(query, params)
)
if (error) console.log(error)
if (!data) return <div>Loading</div>
block.blocks = data
return <BlockCarousel block={block} />
}
export default BlockCarouselBlog
If you want to use GROQ to fetch data this way, and you don't want to do it client side, you probably want to either create a custom source plugin that creates GraphQL nodes using Gatsby's APIs, or define a pre-build function that will fetch all of your data and make it available in static JSON files or similar, then import those into your components or fetch them client side.
Using GROQ with Gatsby without using GraphQL for statically rendered components is going to be a bunch of additional work, and you lose a lot of the value of Gatsby over something like Next.js.

Getting 404 when first loading dynamic routes on nextjs

I'm trying to create a blog page to test nextjs and created a dynamic route for the posts, which will be retrieved from Contentful. When navigating from Home page and clicking into a next/router <Link /> component, the blog post loads correctly, but if I get the URL and try loading the page directly from browser address bar, I'll get 404.
Steps to reproduce:
1. git clone https://github.com/zeit/next-learn-demo.git
2. cd next-learn-demo/8-deploying
3. yarn
4. next build && next export
5. cd out
6. serve
7. Navigate to http://localhost:5000/p/learn-nextjs
8. See 404
Is this a limitation of NextJS (didn't find anything related to it on documentation) or do we need to configure anything else?
The real issue is that exporting a next app will make it generate static HTML files. Even though it will still be able to request data before rendering the page, the set of available paths are not dynamic (they are generated during the next export command). See this docs and this example.
Based on this, I have 2 possible solutions:
generate a webhook to trigger a next build && next export command every time a new blog post is published in Contentful;
avoid exporting my next app and host a Node server that will handle the dynamic routes.
That's because when you directly access the link or refresh the page then it add's a slash at the end of route. An next.js doesn't recognize any route like that. To fix this, I hope there should be an easiest way to do that. However you can do this using custom server. Here is an example:
server.get("/about/", (req, res) => {
return app.render(req, res, "/about")
});
Hope this will help you.
To extend the answer provided by #Minoru, the official Next documentation covers this case in this example.
Using getStaticPaths and getStaticProps allows you to create dynamic pages at build time, avoiding the 404.
Example code for posts dynamic page:
import { GetStaticPaths, GetStaticProps } from 'next';
const Post = ({ post }) => {
const { id, content } = post;
return (
<div>
<h1>Post {id}</h1>
<p>{content}</p>
</div>
);
};
export const getStaticPaths: GetStaticPaths = async () => {
// Get all posts via API, file, etc.
const posts = [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }, { id: '5' }]; // Example
const paths = posts.map(post => ({
params: { id: post.id },
}));
return { paths, fallback: false };
};
export const getStaticProps: GetStaticProps = async context => {
const postId = context.params?.id || '';
// Get post detail via API, file, etc.
const post = { id: postId, content: `I'm the post with id ${postId}!` }; // Example
return { props: { post } };
};
export default Post;
When building the site with next build && next export we will see in the out folder that Next has created each post page
Then, when you navigate to /posts/3/ you will see the post with id 3
For reference, this docs page contains this case and many other use cases.
Don't want to infringe any old posts rules, but in case anyone else in my context I use vercel's feature webhook to new deploys and as I was using firebase I've created a simple firebase function whith is hooked to a new event creation of a page triggers the webhook. I've used fetch because we can make a GET request according to the docs
exports.newEventAdded = functions.region('us-central1').firestore.document('local_events/{localeventId}')
.onCreate((snap, context) => {
fetch('https://api.vercel.com/v1/integrations/deploy/process.env.NEXT_PUBLIC_VERCEL_WEBHOOK_ID')
.then(function (response) {
return response.json();
})
.then(function (myJson) {
console.log(JSON.stringify(myJson));
});
})

Handle URL Parameters with Gatsby

I'm using React & Gatsby to create a website and I've got everything laid out the way I want, I'm just having some problems understanding how to use URL Route parameters to change content that is displayed.
For example, say I have a page categories.js which listens to http://website.com/categories but I want to be able to dynamically handle any URL parameters like such:
http://website.com/categories/animals
When using gatsby-link like so: <Link to="/categories/animals" /> it wants me to create a file called animals.js in a categories folder. Instead, I want categories.js to be able to handle the rendering for this page and select the content appropriate based on the category passed in the URL parameters.
Other than the items displayed, this page is exactly the same through all categories, so it doesn't make sense for every category to have it's own static page.
I think you are wrong when you say:
Other than the items displayed, this page is exactly the same through all categories, so it doesn't make sense for every category to have it's own static page.
In fact, that's exactly where I find GatsbyJS so useful, as it is a static site generator.
It means that you can give Gatsby a template component that will have the same layout for all of your categories, and then Gatsby will fetch data and create static pages for you at build time.
Gatsby is not limited to making pages from files like many static site generators. Gatsby lets you use GraphQL to query your data and map the data to pages—all at build time. (from Gatsby official tutorial)
The idea would be something like this:
in /gatsby-node.js
const path = require(`path`); // you will need it later to point at your template component
exports.createPages = ({ graphql, boundActionCreators }) => {
const { createPage } = boundActionCreators;
// we use a Promise to make sure the data are loaded
// before attempting to create the pages with them
return new Promise((resolve, reject) => {
// fetch your data here, generally with graphQL.
// for example, let say you use your data from Contentful using its associated source plugin
graphql(`
{
allContentfulCategories {
edges {
node {
id
name
description
# etc...
}
}
}
}
`).then(result => {
// first check if there is no errors
if (result.errors) {
// reject Promise if error
reject(result.errors);
}
// if no errors, you can map into the data and create your static pages
result.data.allContentfulCategories.edges.forEach(({ node }) => {
// create page according to the fetched data
createPage({
path: `/categories/${node.name}`, // your url -> /categories/animals
component: path.resolve('./src/templates/categories.js'), // your template component
context: {
// optional,
// data here will be passed as props to the component `this.props.pathContext`,
// as well as to the graphql query as graphql arguments.
id: node.id,
},
});
});
resolve();
});
});
};
And then you can simply fetch the data on your template component
in /src/templates/categories.js
import React from "react";
export default ({ data: { contentfulCategories: category } }) => {
return (
<div>
<h1>{category.name}</h1>
<div>{category.description}</div>
</div>
);
};
// we can query the needed category according to the id passed in the
// context property of createPage() in gatsby-node.js
export const query = graphql`
query CategoryQuery($id: String!) {
contentfulCategories(id: { eq: $id }) {
name
description
}
}
`;
If you insist in dynamically rendering your categories pages, you can take example of this question that is sort of similar to yours, but note that you will have to manually handle re-rendering if the page's props changes in the componentWillReceiveProps lifecycle method of the React class component.
But I really don't think this is a reliable solution.
EDIT:
I've found a better answer after searching forever! -
https://github.com/gatsbyjs/gatsby/issues/13965#issuecomment-617364363
the example for your case I imagine would be something like this:
gatsby-node.js:
createPage({
path: `/categories/:id`,
matchPath: `/categories/:id`,
component: path.resolve(`./src/pages/categories.js`),
})
categories.js:
import React from "react"
export default function Booking({ id }) {
return <div>categories #{id}</div>
}
OLD ANSWER:
I seem to have come across exactly the same issue too and couldn't find any answers. My idea was to also use the gatsby-node.js file, but I don't query anything using graphQL.
My version of /src/templates/categories.js:
const path = require(`path`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
createPage({
path: `/categories/animals`,
component: path.resolve(`./src/pages/categories.js`),
// The context is passed as props to the component as well
// as into the component's GraphQL query.
context: {
id: `animals`, //use this context parameter to do the dynamic stuff in your page
},
})
}
I hope this is useful.

Resources