How to create a root component for making a request regardless of page url in next.js? - javascript

I am currently trying to make a request to server after refreshing page or opening this in a new tab.
For example after using f5 I want to make a request. But I don't want to make a request after every routing.
Basically after refreshing I want to make a request to see if user is authenticated using cookie on the request.
But It appears next.js doesn't have a root component like pure react.js(App.js). I searched documentations and different websited. But I couldn't find it. I don't want to write request code to every file.
Here is my files:
Is there a way to do this?
I want to do a request on every link possible (/login, /register, /profile, /) So I need a root component which is mounted on every link (only once).

You need to create _app.ts file in your pages directory. and add the following snippet.This component is the root component in next js and it any function you write here will be executed once depending on how you call this function.
for more info, check this link in Next Js docs https://nextjs.org/docs/advanced-features/custom-app
// import App from 'next/app'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// MyApp.getInitialProps = async (appContext) => {
// // calls page's `getInitialProps` and fills `appProps.pageProps`
// const appProps = await App.getInitialProps(appContext);
//
// return { ...appProps }
// }
export default MyApp

You can build a function to wrap around your "normal" pages to use it. You can pass arguments, in my example a country option.
export default function Layout(
{country, children}: PropsWithChildren<{country: Country }>) {
/* your code inside here */
return (
<main className={country} data-gtm-block="main">
{children}
</main>
);
}
and then use it like that for every page
export default function AboutPage({ country }: { country: Country }) {
return (
<Layout country={country}>
/* your code in here */
</Layout>
);
}
export const getStaticProps: GetStaticProps = async (context: GetStaticPropsContext) => {
const country = getCountry(context.locale);
return {
props: {
country
}
};
};

Related

NextJs GetServerSideProps after update how can i call?

Below I am pulling all the data from the database. But let's say I deleted one piece of data. How can I retrieve the renewed data? How can I run it again?
export async function getServerSideProps() {
const res = await fetch(`http://localhost:8000/api/getAllShipmentTypes`);
const shipmentTypes = await res.json();
return {
props: { shipmentTypes } // will be passed to the page component as props
};
}
But let's say I deleted one piece of data. How can I retrieve the renewed data?
I think you will need to define what is the trigger for the deletion, I can think of these two.
Another action user performs on a page.
Some other system modifying the database that this client application shows.
For #1, To the action, say a button click you can use a router object to set the same route again which will run getServerSideProps again
When you request this page on client-side page transitions through next/link or next/router, Next.js sends an API request to the server, which runs getServerSideProps
For #2 - this would be handled by giving the user an option to refetch the data from the server again using a link or router component
You can do something like:
import { useRouter } from 'next/router';
function SomePage(props) {
const router = useRouter();
// Call this function whenever you want to
// refresh props!
const refreshData = () => {
router.replace(router.asPath);
}
}

Does Nextjs generate duplicate pages at build time?

So I haven't found this answer anywhere and I would like to know this so that's why posting here. Suppose I have a blog for which I want to use SSG and ISR. SSG for the homepage and ISR for the individual posts.
So if I generated the homepage at build time with SSG to display 10 posts with the following code:
index.js file:
export const getStaticProps = async () => {
const posts = await get10PostsFromDB();
return {
props: {
posts
}
}
}
And then, if I use this code for each post and also use getStaticPaths to generate those exact 10 posts:
[post].js
export const getStaticPaths = async () => {
const posts = await get10PostsFromDB();
const paths = posts.map( ( item ) => ( {
params: {
slug: item.slug
}
} ) );
return {
paths,
fallback: 'blocking'
};
}
export const getStaticProps = async ({ params }) => {
const post = await getASinglePostFromDB( params.slug );
return {
props: {
post
},
revalidate: 1,
notFound: true
}
}
Will there be duplicate SSG generated pages as 10 are generated at build time for the homepage and then again those 10 pages are generated for getStaticPaths as well? Or what I'm thinking is totally wrong?
I think you have a bit of a misunderstanding of what getStaticPaths and getStaticProps do.
Short answer: no, it will not duplicate the pages.
Let me explain why:
getStaticProps vs getStaticPaths
getStaticProps (Static Generation): Fetch data at build time.
getStaticPaths (Static Generation): Specify dynamic routes to pre-render pages based on data.
What does getStaticProps do?
NextJS has this built-in page pre-rendering but this built-in process has a flaw if you wanna call it like this.
Take this code for example:
const HomePage = () => {
const [loadedPosts, setLoadedPosts] = useState();
useEffect(() => {
setLoadedPosts(DUMMY_POSTS);
}, []);
return (
loadedPosts ? (
<PostList posts={loadedPosts} />
) : ''
)
}
Note: This is a simple demo to convey the concept. In normal cases this issue will occur when you fetch data from an API in the useEffect hook.
Explanation: Let's imagine we are rendering a list of posts. DUMMY_POSTS is our data that comes from the db for example.
What happens here is that the useEffect hook will wait for the component to render before updating out loadedPosts.
When NextJS pre-renders a page it takes the snapshot of the first component rendering cycle as its content and that might be missing crucial data. This means that NextJS does not update the snapshot of our code AFTER the new data has come. This as you know is bad for SEO.
After this HTML page was received on the client, React will actually take over, the page is hydrated as this process is called, which means that now React will turn this into a single page application, take over control, and then this useEffect function will be executed, data might be fetched and the page might be updated - not on the server, not on the pre-rendered page but instead after this page was received in the browser. I.e no SSR.
Here's where getStaticProps() comes to play.
const HomePage = (props) => {
return (
<PostList posts={props.posts} />
)
}
export function getStaticProps() {
// fetch data from an API
return {
props: {
posts: DUMMY_POSTS
},
revalidate: 10
};
};
export default HomePage;
getStaticProps runs at build time and it fetches the data DUMMY_POSTS and then assigns it to props. This is then passed as props of the HomePage component where we can access props.posts.
What is getStaticPaths?
getStaticPaths is a function that you need to export in a page component file that is:
A dynamic page and
Is using getStaticProps
You DO NOT need it in a file that is using getServerProps and DO NOT NEED IT WITHOUT getStaticProps
Here I will use a very simple example to explain, but normally you generate the paths dynamically as you have done in your example:
export async function getStaticPaths() {
return {
fallback: false,
paths: [
{
params: {
postdId: 'p1'
}
},
{
params: {
postId: 'p2'
}
},
]
};
};
In the above example, we have two posts with IDs p1 and p2.
With getStaticProps a page is generated during the build process. This means that Next.js needs to pre-generate all versions for all dynamic pages in advance for all supported dynamic paths. Since it's dynamic Next.js needs to know for which IDs it needs to pre-generate a page. These functions are run during the build process NOT when a client visits the page. I.e. without getStaticPaths these dynamic pages will not be pre-generated on the server which kills the purpose of Next.js.
getStaticPaths tells Next.js what are the exact dynamic paths (in our case which post IDs) it needs to pre-generate. If the user enters an ID for which we did not pre-generate a page they will see a 404 error.
getStaticPaths returns an object where we describe all of the dynamic segment values. It has a paths property, which is an array that takes an object for each version of the dynamic page. This object has a params property, which is also an object that holds all of the key-value pairs that lead to your dynamic page.
TLDR;
getStaticProps fetches the data at build time before the component has loaded so that Next.js can pre-render the page WITH the data that we are fetching.
getStaticPaths tells Next.js about dynamic pages that need to be pre-rendered, because Next.js will not pre-render them on its own as it does not know exactly the dynamic IDs (routes). With this function you are basically telling Next.js how your dynamic routes are called so it knows what to pre-render. Otherwise it will show a 404 page.
Conclussion:
NextJS does not generate duplicate pages at build time.
EDIT:
In regards to pre-generating pages with getStaticPaths...
You DO NOT need to pre-generate every single dynamic route. In order to be able to load the other routes without a 404 error, you can add fallback: true or fallback: 'blocking'
These options can help in cases where you have a lot of pages that need to be pre-generated. In the case we have a website like Amazon with millions of products, pre-rendering all of them might take super long and there may be products that are rarely visited. Therefore, pre-generating such products is a waste of resources.
In such cases, we can set fallback: true and pre-generate some pages, not all.
export async function getStaticPaths() {
return {
fallback: true,
paths: [
{
params: {
prodId: 'p1'
}
}
]
}
}
Even if we don't list all of the pages, this setting tells NextJS that other pages can be valid and should be loaded when they are visited. They are generated just in time. This allows to pre-generate highly visited pages and pre-generate other pages only when they are needed.
However, if the request with the unsupported ID is sent directly (not internally through clicking on a product), it will return a 404. In such cases, we need to have a fallback case in our component.
const ProductDetailPage = ({ loadedProducts }) => {
if (!loadedProducts) {
return <p>Loading...</p>
}
return (
<>
<h1>{loadedProducts.title}</h1>
<p>{loadedProducts.description}</p>)
</>
)
}
In the above code, the user will see the Loading indicator instead of a 404 page, until the data comes and is then displayed on the page.
An alternative would be to set fallback: 'blocking'. In those cases, we don't need a fallback in the component, but the response will take a little longer.
const ProductDetailPage = ({ loadedProducts }) => {
return (
<>
<h1>{loadedProducts.title}</h1>
<p>{loadedProducts.description}</p>)
</>
)
}
export async function getStaticPaths() {
return {
fallback: 'blocking,
paths: [
{
params: {
prodId: 'p1'
}
}
]
}
}
It depends on your case. Sometimes you need to show something quickly (fallback: true). Sometimes it's worth waiting for it and you don't want to show an incomplete page to your visitors (fallback: 'blocking').
Read more about NextJS's Data fetching here.

Next.js: Skipping generating static sites at build time (instead: only at runtime, as getStaticProps has no data in build phase)

I want to use the same application for different customers, where there's a different database for each (which could be on-premise).
As a result, I have no data at the build phase (e.g. in CI/CD) that I could use to create static sites.
I thought about skipping generating the sites in getStaticProps via an environment variable.
When building the sites, I could tell Next.js to not use any data, something like this:
export const getStaticProps: GetStaticProps<HomePageProps> = async () => {
const isBuildPhase = process.env.IS_BUILD;
const data = isBuildPhase ? null : await fetchData();
return {
props: {
data: data ?? null,
},
revalidate: 5 * 60,
};
};
Now, I want to create the sites only at runtime (with next start), because at runtime, the built application has access to its database. In each getStaticProps the environment variable will be configured so data is fetched. When the application starts initially, it will generate all static sites when they're accessed.
Are there big downsides with this approach?
Are there maybe better solutions to this problem?
We had a similar requirement where the app needed to serve multiple tenants pages, but each tenant data is different. Which meant the app did not have access to data at build time and only runtime.
We leveraged getStaticPaths and getStaticProps to do this.
getStaticPaths
This method returns a fallback option (true || false || blocking), which we can use to decide to show a loader on the UX or block until the actual page loads.
export async function getStaticPaths() {
// Return empty paths because we don't want to generate anything on build
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return {
paths: [],
fallback: 'blocking',
};
}
When using this method, there isn't a need to maintain IS_BUILD env variable. (Depending on your specific use-case you may choose to use it or not)
getStaticProps
This method does the actual data fetching based on the URL path params for a tenant.
export async function getStaticProps({ params }) {
// Run your data fetching code here
const data = await fetch(params);
return {
props: data,
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 10 seconds
revalidate: 10, // In seconds
notFound: !data,
};
}
We also leverage Incremental Static Regeneration, to make sure that we refresh the page every revalidate seconds.
Pages
Single Template Tenants
When all the tenants share the same components, it's pretty straight forward:
const SingleTenantPage = ({ data }) => {
return <Component {...data} />;
};
export default SingleTenantPage;
Multi-Template Tenants
The actual page, just parses the props (passed down from getStaticProps), and then loads the appropriate component for that page.
This way we leverage a single route for multiple tenants. example.com/app/tenantA, example.com/app/tenantB, example.com/app/tenantC all 3 routes can be served out of pages/app/[slug]/index.js.
Using dynamic imports, we make sure that only the component for a specific tenant is loaded. (also helps in code splitting)
// Dynamic Import so we load only the required bundles
const templates = {
tenantA: dynamic(() => import(`../templates/tenantA`)),
tenantB: dynamic(() => import(`../templates/tenantB`)),
tenantC: dynamic(() => import(`../templates/tenantC`)),
};
const MultiTenantPage = ({ data }) => {
// Provided template is present in the data
// template: 'tenantA' || 'tenantB' || 'tenantC'
const { template, ...rest } = data || {};
// If the template doesn't exist, show a 404 Page instead
const Component = templates[template] || (() => <Error statusCode="404" />);
return <Component {...rest} />;
};
export default MultiTenantPage;
The multi-tenant page works well for us. We use a larger revalidate value because data isn't changing that often. Only caveat we had were a little bit complex test cases because of all the dynamic imports.
The above solution is very much similar to what you've considered for your use-case, albeit without an additional env variable (IS_BUILD)

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.

useQuery after server-side rendering

I'm using next.js and apollo with react hooks.
For one page, I am server-side rendering the first X "posts" like so:
// pages/topic.js
const Page = ({ posts }) => <TopicPage posts={posts} />;
Page.getInitialProps = async (context) => {
const { apolloClient } = context;
const posts = await apolloClient.query(whatever);
return { posts };
};
export default Page;
And then in the component I want to use the useQuery hook:
// components/TopicPage.js
import { useQuery } from '#apollo/react-hooks';
export default ({ posts }) => {
const { loading, error, data, fetchMore } = useQuery(whatever);
// go on to render posts and/or data, and i have a button that does fetchMore
};
Note that the useQuery here executes essentially the same query as the one I did server-side to get posts for the topic.
The problem here is that in the component, I already have the first batch of posts passed in from the server, so I don't actually want to fetch that first batch of posts again, but I do still want to support the functionality of a user clicking a button to load more posts.
I considered the option of calling useQuery here so that it starts at the second "page" of posts with its query, but I don't actually want that. I want the topic page to be fully loaded with the posts that I want (i.e. the posts that come from the server) as soon as the page loads.
Is it possible to make useQuery work in this situation? Or do I need to eschew it for some custom logic around manual query calls to the apollo client (from useApolloClient)?
Turns out this was just a misunderstanding on my part of how server side rendering with nextjs works. It does a full render of the React tree before sending the resulting html to the client. Thus, there is no need to do the "first" useQuery call in getInitialProps or anything of the sort. It can just be used in the component alone and everything will work fine as long as getDataFromTree is being utilized properly in the server side configuration.

Categories