I am building a NextJS application, currently I am using getStaticPaths and getStaticProps to build the static pages, doing the necessary requests for them.
So I want to build all the pages following this url: challenge/[slug]/ and for each slug that corresponds to an id I want to have a applications page like this: challenge/[slug]/applications to archive this I builded a file [...slug] inside /pages/challenge
Inside that file I have the following code to handle the static generation:
export async function getStaticPaths() {
const response: any = await getPrograms()
const paths = response.results.map(result => {
return { params: { slug: [result.id.toString()] } }
})
return { paths, fallback: true }
}
export async function getStaticProps({ params }) {
const res = await getProgram(params.slug[0])
const stages = await getStages(params.slug[0])
return { props: { program: res, stages: stages }, revalidate: 1 }
}
this solution works for /challenge/[slug], but the /challenge/[slug]/applications receives a 404, how can I render a specific application page for the slug?
I tried to add a second position to the slug array, but if I do it I can just render /challenge/[slug]/applications and not /challenge/[slug]
Any advice?
Thanks!
Firstly, You need to create a FOLDER named [slug]. Then, Create a FILE named applications.js. Lastly, copy and paste that code into this page.
__ challenge
|__ [slug]
|__ applications
In this page you can get or set slug as your desired parameter.
Related
I have SPA page, all work very good but when user reload page beeing on winners or garage get info :
Cannot GET /Garage. Then have to pick default url. How to set reload function on current page.
https://darogawlik-async-race-api.netlify.app/ (my app)
const navigateTo = url => {
history.pushState(null, null, url)
router()
}
const router = async () => {
const routes = [
{ path: '/Garage', view: garage },
{ path: '/Winners', view: winners },
]
// Test each route for potential match
const potentialMatches = routes.map(route => ({
route,
isMatch: location.pathname === route.path,
}))
let match = potentialMatches.find(potentialMatches => potentialMatches.isMatch)
if (!match) {
match = {
route: routes[0],
isMatch: true,
}
}
const view = new match.route.view(document.querySelector('#main'))
}
window.addEventListener('popstate', router)
document.addEventListener('DOMContentLoaded', () => {
document.body.addEventListener('click', e => {
if (e.target.matches('[data-link]')) {
e.preventDefault()
navigateTo(e.target.href)
}
})
router()
})
window.addEventListener('load', router())
This will be a problem with default document handling in the web host - it is not a page load problem. Eg just click this link to get the problem:
https://darogawlik-async-race-api.netlify.app/Garage
Since you are using path based routing, your web host must serve the default document for all paths, including /Garage and /Winners. As an example, in Node.js Express you write code like this. For other web hosts you either write similar code or there is a configuration option that will do it for you.
// Serve static content for physical files, eg .js and .css files
expressApp.use('/', express.static());
// Serve the index.html for other paths
expressApp.get('*', (request, response) => {
response.sendFile('index.html');
}
According to this post on Netlify, you can add a file something like this. I'm no expert on this platform, but hopefully this gives you the info you need to resolve your issue:
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
I have set up an ACF options page in WordPress called Projects
Inside the Projects options page there is an ACF repeater allowing the user to add multiple Projects.
In Gatsby, I’m using Graphql to query the data for my Projects in two files:
Inside a custom hook, allowing access to the data globally in my Gatsby site
Inside a gatsby-node.js file in order to generate a slug for my template page called project-details.js
Obviously there is no slug in Graphql for this repeater field in the ACF options page. Instead, I’m generating a slug based on a nested Title text field that’s found inside each Project repeater field.
I’m using both the replaceAll() and toLowerCase() methods to create the slug and then making it available as part of my data.
Here's my custom hook:
export const useProjectsQueryAlt = () => {
const data = useStaticQuery(graphql`
query ProjectsQueryAlt {
wp {
projects {
projects {
allprojects {
projectContent
projectTitle
featuredImage {
mediaItemUrl
id
}
projectGallery {
caption
id
mediaItemUrl
}
}
}
}
}
}
`)
const project = data.wp.projects.projects.allprojects.map(node => {
const { projectContent, projectTitle, featuredImage, projectGallery } = node;
const title = node.projectTitle;
const spacesToHyphen = title.replaceAll(' ', '-');
const slugFromTitle = spacesToHyphen.toLowerCase()
return {
projectContent,
projectTitle,
slug: slugFromTitle,
featuredImage,
projectGallery: projectGallery.map(node => {
const { caption, id, mediaItemUrl } = node;
return {
caption,
id,
mediaItemUrl
}
})
}
})
return { project }
}
Here's my gatsby-node file:
const path = require('path')
exports.createPages = async ({ graphql, actions }) => {
const { data } = await graphql(`
query Projects {
wp {
projects {
projects {
allprojects {
projectTitle
}
}
}
}
}
`)
data.wp.projects.projects.allprojects.forEach(node => {
const title = node.projectTitle;
const spacesToHyphen = title.replaceAll(' ', '-');
const slugFromTitle = spacesToHyphen.toLowerCase()
actions.createPage({
path: '/projects/' + slugFromTitle,
component: path.resolve('./src/templates/project-details.js'),
context: { slug: slugFromTitle },
})
})
}
Here's my template file project-details.js
import React from 'react'
function ProjectDetails() {
return (
<div>
...my page template content
</div>
)
}
export default ProjectDetails
I now need to find a way to check that the two appended slugs match in my ‘project-details.js’ template file in order to display the relevant project data to the corresponding URL.
Seeing as I’ve generated my slugs on the front end, following the Gatsby Docs for setting up dynamically generate pages doesn’t align with my use case. I was hoping somebody has had experience with this use case and can point me in the right direction.
The problem in your approach is that you are generating a "fake" slug based on the title of the project so you can't use that field to filter any GraphQL node because the field is not present in the project fields. Your best option is using the title itself or using any autogenerated identifier (id, if it's present as a field).
actions.createPage({
path: '/projects/' + slugFromTitle,
component: path.resolve('./src/templates/project-details.js'),
context: { title },
})
Note: you can omit { title: title }
You can still use the path of your generated slug, this is a valid approach.
I'm assuming that if the title is a unique field, the slug must be too, hence you will be a valid filter.
Now in the project-details.js:
import React from 'react'
function ProjectDetails({ data }) {
console.log("my data is", data);
return (
<div>
...my page template content
</div>
)
}
export const query = graphql`
query($title: String!) {
yourACFNode(title: { eq: $title} ) {
# your fields
}
}
`
export default ProjectDetails
Of course, tweak the query above to match your ACF node but get the approach.
I am trying to understand why Next.js is building some of my pages as SSG and some of them as Static, when they all are using getStaticProps.
Let's take my 404 page that uses getStaticProps to fetch data from prismic with graphql. It is being rendered as a Static website when in my opinion it should be rendered as SSG (because it uses getStaticProps).
I am doing the EXACT same thing in my 500 page, but with a different graphql query and it is being rendered (in my opinion correctly) as SSG.
Why is that?
404 page:
const NotFound = ({ data: { page } }) => {
return (
<div className={'not-found'}>
<p className={'not-found__description'}>{RichText.asText(page.description)}</p>
</div>
);
};
export const getStaticProps = async (context) => {
const currentLanguage = getCurrentLocale(context);
const response = await apolloClient.query({
query: gql`
query {
}
`
};
return {
props: {
data: {
page: response
}
}
}
});
export default NotFound;
500 page:
const InternalServerError = ({ data: { page } }) => {
return (
<div className={'internal-server-error'}>
<p className={'internal-server-error__description'}>{RichText.asText(page.description)}</p>
</div>
);
};
export const getStaticProps = async (context) => {
const currentLanguage = getCurrentLocale(context);
const response = await apolloClient.query({
query: gql`
query {
}
`
});
return {
props: {
data: {
page: response
}
}
}
};
The 404.tsx or 404.js page in Next.js is unique in that it does not rely on the server and is always Static -- relying solely on static html (no json) at build time -- even when using GetStaticProps in the file.
The 404 page is simply a catch all funnel route that users are redirected to when navigating to paths that do not exist with your site as the base URL. So, it doesn't rely on the server on initial build. It's the fallback for paths not existing, and nothing else. The 500 page, on the other hand, handles an internal error in your application so it does rely on both .html and .json file types to pinpoint the nature of the error.
Interestingly, if you examine the contents of your .next directory locally, you'll notice that all pages using GetStaticProps have .json and .html files statically generated. Pages using GetStaticProps with revalidate returned === Incremental Static Regeneration, or ISR. ISR is an ideal hybrid of SSG and SSR, having background functions scanning for incoming changes/updates in production (the number you specify being the amount of time in seconds between possible updates). So, pages with GetStaticProps + ISR generate three file types in the .next directory -- .html, .json, and .js. That said, Pages using GetServerSideProps or GetInitialProps have only .js files generated in the .next directory. Lastly, pages that are purely Static, using none of the aforementioned methods, have only .html files generated.
The idea behind the 404 page and its Static nature is to enhance UX by expediting the rendering (or more correctly prerendering) of a custom oops! that path doesn't exist page so that a user can return to the actual application asap.
For example, I have the following in my 404.tsx page, but it still renders as Static html at build time.
import { Container } from '#/components/UI';
import { initializeApollo, addApolloState } from '#/lib/apollo';
import { NotFound } from '#/components/NotFound';
import { AppLayout } from '#/components/Layout';
import {
GetStaticPropsContext,
GetStaticPropsResult,
InferGetStaticPropsType
} from 'next';
import {
NotFoundQuery,
NotFoundDocument,
NotFoundQueryVariables,
DynamicNavQuery,
DynamicNavDocument,
DynamicNavQueryVariables,
WordpressMenuNodeIdTypeEnum,
WordpressMediaItemSizeEnum,
WordpressPageIdType
} from '#/graphql/generated/graphql';
export function SOS({
notFound,
Header,
Footer
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<>
<AppLayout title={'✂ 404 ✂'} Header={Header} Footer={Footer}>
<Container clean className='fit'>
<NotFound notFound={notFound} />
</Container>
</AppLayout>
</>
);
}
export async function getStaticProps(
ctx: GetStaticPropsContext
): Promise<
GetStaticPropsResult<{
notFound: NotFoundQuery['NotFound'];
Header: DynamicNavQuery['Header'];
Footer: DynamicNavQuery['Footer'];
}>
> {
const params = ctx.params!;
console.log(params ?? '');
const apolloClient = initializeApollo();
await apolloClient.query<
DynamicNavQuery,
DynamicNavQueryVariables
>({
query: DynamicNavDocument,
variables: {
idHead: 'Header',
idTypeHead: WordpressMenuNodeIdTypeEnum.NAME,
idTypeFoot: WordpressMenuNodeIdTypeEnum.NAME,
idFoot: 'Footer'
}
});
await apolloClient.query<NotFoundQuery, NotFoundQueryVariables>(
{
query: NotFoundDocument,
variables: {
id: '/404-not-found/' as '/404/',
idType: WordpressPageIdType.URI,
size: WordpressMediaItemSizeEnum.LARGE
}
}
);
return addApolloState(apolloClient, {
props: {},
revalidate: 60
});
}
export default SOS;
Interestingly, because I do use GetStaticProps and revalidate for ISR in my 404.tsx page, the contents of the .next directory reflects this as all three file types are present for 404 (.js, .json, .html). If you use getInitialProps in your custom _app.tsx or _app.js file, then automatic static optimization (the prerendering of static pages) will be disabled across the entirety of your app. Give it a try if you're curious, it should cause the 404 page to have a lambda next to it in your build log. However, since you already have GetStaticProps that should override the app-wide static deoptimization caused by your root app page using GetInitialProps
For example, I used GetInitialProps in _app.tsx prior to creating a custom 404.tsx page some time back. I decided to pull the build logs and took an accompanying screen shot.
Warning: You have opted-out of Automatic Static Optimization due to `getInitialProps` in `pages/_app`. This does not opt-out pages with `getStaticProps`.
Is the 404 page missing that parenthesis in your code?
const response = await apolloClient.query({
query: gql`
query {
}
`
};
should be
const response = await apolloClient.query({
query: gql`
query {
}
`
});
I have been working on this site and I have hit a wall. Basically I am supposed to list movies by genre, fetched from database. The genre should take me to another list based on the genre. Once a user clicks the movie from say 'action' genre it takes them to the movie details on another page.
This is the structure
Movies/ [moviesbygenrelist]/list
Everything works till there.
Moving on to the second dynamic page I cannot get values of first and second dynamic page
as below...
Movies/ [moviesbygenrelist]/[movie-slug]
I am statically generating the site
how can i get parameters of first page while on the second dynamic page
This is what i have,
I first call
let movieTypeID;
let movieSlug;
export async function getStaticProps({params}) {
movieTypeID=params.movietype;
movieSlug=params.movie;
}
my logic is i can access route parameters from getStaticProps but not in getStaticPaths so I call it first, instantiate the variables then pass them to getStaticPaths so I can make database calls using the variables since I am now a bit deep in the database. I cannot make calls without the dynamic parameters
I pass them like below
export async function getStaticPaths(movieTypeID, movieSlug) {
///only they come out as undefined
}
Assuming the page is located under pages/movies/[type]/[slug].jsx in your Next.js app:
// pages/movies/[type]/[slug].jsx
export async function getStaticPaths() {
const movies = db.getAllMovies() // Retrieve all movies data from database
const paths = movies.map((movie) => ({
params: { type: movie.type, slug: movie.slug },
}))
return {
paths,
fallback: false // Paths not returned will result in a 404
};
}
export async function getStaticProps({ params }) {
const { type, slug } = params
const movieData = getMovie(type, slug) // Retrieve data for given type/slug pair
return {
props: {
data: movieData
}
}
}
function Movie({ data }) {
//render the given movie data
}
export default Movie
This will statically generate pages for all movies in your database. Each page will be available at /movies/<movie-type>/<movie-slug> in the browser.
I'm starting with Next.js and after going through docs, I cannot figure out how to get the route param code inside getStaticPaths method as shown below!?. code is not known before hand by any means and it can be anything.
I don't want to call api and get the data using useEffect inside the component.
File: pages/post/[code].js
import React from 'react';
import apiCall from 'api/something';
export default ({post}) => {
return <>
render components here based on prop `post`
</>
}
export async function getStaticPaths() {
// How to get [code] from the route here, which can be used below?
return {
paths: // NEED [code] HERE from current route,
fallback: false
}
}
export async function getStaticProps(ctx) {
return {
props: {
// [ctx.code] resolved from current route with the help of getStaticPaths,
post: apiCall(ctx.code)
}
}
}
I've tried getServerSideProps which works for me:
export const getServerSideProps = async (ctx) => {
return {
props: {
post: await apiCall(ctx.query.code)
}
};
};
But it fails when I do next export stating:
pages with getServerSideProps can not be exported. See more info here: https://err.sh/next.js/gssp-export
After investigating further on this error I found this solution, which is not feasible for me as my app is hosted on Heroku.
I'm trying to server-side render the html along with the data based on the route param code. But not able to do so now.
The purpose of the function getStaticPaths is to generate a list of paths for which static HTML will be rendered at build time. For example, for a list of 10 posts, you can generate 10 posts/[id] routes ahead of time if you know the id of the posts.
How getStaticPaths works with dynamic routes in more details..
Suppose you have a dynamic route /posts/[postId] if you choose to use static-generation you have to generate a list of paths that will include the postId as a route param and for each path returned, the function getStaticProps will be called to query the data at build time. Example,
// for /post/[postId]
export const getStaticPaths = async () => {
// if you know all the postId ahead of time
const paths = [
{ params: { postId: '1234' } }, // keep in mind postId has to be a string
{ params: { postId: '3792' } },
{ params: { postId: '1749' } },
]
return {
paths,
fallback: false // we are disabling fallback because we know all the paths ahead of time
}
}
// for each path returned getStaticProps will be called at build time
export const getStaticProps = async (context) => {
// you have access to the postId params that you returns from
// getStaticPaths here
const postId = context.params.postId
// now you can query the data from postId and return as props
return {
props: // queried data
}
}
If fallback is set to false any for any route path that is not returned from the function getStaticPaths nextjs will simply show a 404 error page.
How to use fallback: true to generate static pages for route params not known ahead of time
If you know some postId of the posts and the data for the posts do not change very often, you can choose to generate the pages with fallback property set to true, which will display a fallback version of the page for the paths that are not returned from the function getStaticPaths. And on request for the page nextjs will call getStaticProps and send the data as JSON which will be used to render the page in the browser.
Example,
// for /post/[postId]
export const getStaticPaths = async () => {
// you can get how many ever postIds are know ahead of time
// and return as paths with fallback set to true
const posts = // queried data from db or fetched from remote API
const paths = posts.map(post => { params:{ postId: post.id.toString() }})
return {
paths,
fallback: true
}
}
// in your page Component check for fallback and render a loading indicator
import { useRouter } from 'next/router';
const MyPage = (props) => {
// before you do anything
const router = useRouter();
if (router.isFallback) {
return <div>Loading....</div>
}
// rest of your page logic
}
If your data is very dynamic, let's say changing every 30mins or an hour or so. You can choose to use server-side rendering which will fetch the data on per request basis, but TTFB(time to first byte) will be higher. For example,
// for /post/[postId]
export const getServerSideProps = async (context) => {
// you also have access to the param postId from the context
const postId = context.params.postId
// query the data based on the postId and return as props
return {
props: // queried data
}
}
Keep in mind if you choose to go with getServerSideProps the function will be called on per-request basis so time to first byte will be higher.
Depending on use-cases you can also use static generation with client-side data fetching using swr from nextjs team repo link.
As I understand, you want to statically generate dynamic routes at build time.
To do so you need to let Next.js know what pages to generate, by specifying all codes.
export async function getStaticPaths() {
// you don't need here a code from current route
// but you need to specify all known post codes
return {
paths: [
{ params: { code: '1' } },
{ params: { code: '2' } },
{ params: { code: '3' } },
]
fallback: false
}
}
You would need to re-build app every time you change the posts.
Use getServerSideProps if you don't want to re-build project every time. Then the data would be fetched at request time. You can't export it because it requires Node.js server.