In Next.js 13 app directory, how do I incrementally generate new pages? - reactjs

I have multiple items from a CMS. /items/1 all the way to /items/9999. The content is immutable, so I don't have to worry about revalidateing them.
However, items do get added to the CMS frequently, maybe multiple times in a day. I want to make a static website. How can I add new static pages incrementally?
The CMS isn't handled by me, so there's no way I can add a hook.

As per the docs, by default, route segment parameters that were not statically generated at build-time by generateStaticParams function will be generated on demand. These non-generated segments will use Streaming Server Rendering. This is basically the equivalent to fallback: true on getStaticPaths function on pages folder page components.
Just make sure to perform the appropriate checks on your page component in case the requested data doesn't exist in the CMS. That way you can throw a Not Found error and render a 404 UI making use of the not-found.js file. Example from the docs:
import { notFound } from 'next/navigation';
export default async function Profile({ params }) {
const user = await fetchUser(params.id);
if (!user) {
notFound();
}
// ...
}

Related

Create a catchall dynamic route in remix

I'm using remix to serve my react application.
All my pages have dynamic slugs, therefore I need to find a way to resolve the following type of URL:
eg. mywebsite.com/dynamic_slug
If I create an $index.jsx file in the routes folder it works in that all the dynamic URLs resolve to that file, BUT, I can't seem to find a way to then read the slug in the compontent so that I serve the right data.
Many thanks to any responders.
You access the dynamic params via the params object passed to your loaders and actions.
// routes/$index.jsx
export async function loader({request, params, context}) {
const slug = params.index // whatever $name is
//...
}
https://remix.run/docs/en/v1/guides/data-loading#route-params

Can I avoid a double-API call with NextJS' getServerSideProps?

I'm tinkering with NextJS' getServerSideProps. I see that when I request a page from scratch, I receive the fully hydrated content. Then when I navigate to a new page, an API call is made, which receives some JSON data that is used to re-populate the page.
What I don't like is that the new API call is actually making two calls. For example my getServerSideProps has an axios.get() call. So on that click to the new page, I'm getting:
a call to something like example.com/_next/data/1231234/....
that call, behind the scenes, must be running my getServerSideProps() with its axios.get() to retrieve the new JSON data.
So is there a way to avoid the double-API call? I'd prefer that after the first page load, clicks to new pages would just skip to step two.
On a non-NextJS app I'd have something like a useEffect() that ran on page load, but obviously then the first run of the page would not return the full content, and for search-engine purposes I'd like to return the full content. I've seen some lectures where Google says they do run javascript and see the full content, but might as well be on the safe side for all other engines.
getServerSideProps will always run at request time--whenever you hit the page (or possibly using prefetch, the default, of next/link) This will result in pre-render of the page using the data from getServerSideProps Side-note: If you using next API middleware, then you can avoid the ajax call and simply import the method to run directly in getServerSideProps.
It sounds like you want to fetch the data at build time and could render the page statically? If so, rather look to use getStaticProps.
You can also avoid both and make an API call in useEffect if you prefer, but code will be run at the client, once the page loads, of course. getServerSideProps will pre-render the page with the data before it renders to the client.
So, the goal is to determine ways of getting props for:
the initial (direct) page request,
in-app navigation request
To solve this we have two options. And unfortunately, both of them are not perfect.
First option:
Check if the request has header x-nextjs-data. NextJS adds this header for fetching data from getServerSideProps:
export const isInitialPageRequest = (req: GsspRequest) => {
return !req.headers['x-nextjs-data'];
}
export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext) => {
if (isInitialPageRequest(context.req)) {
// this code runs only on the initial request, on the server side
}
return {
props: {
// props
}
}
}
In this case, the request to /_next/data/development/xxx.json?...' is still executed every time. But at least you can control behavior depending on the case (and avoid redundant API calls for example).
Second option:
Use getInitialProps and check if property context.req is defined or not. You already mentioned it in the comments, just added it as an answer option with an example:
page.getInitialProps = async (context: NextPageContext) => {
if (context.req) {
// this code runs only on the initial request, on the server side
}
return {
// props
}
}
The NextJS team is recommending to use getServerSideProps instead

Best way to speed up a big React project by using "Snapshots" of critical routes (static renders)

We have a large and complex traditional React app that we've been building for the last couple of years. It loads an index.html injects javascript and gets data from an API as is usual. Unfortunately, cold load times are pretty bad (5 - 7 seconds on average). Once everything loads, it's snappy as usual but the cold load times are killing us in specific "critical" pages. These are our public user pages, in the format of: https://mywebsite/userId
We're looking for a way to dramatically speed up loading times for these routes, with methods that go beyond code-splitting or resource optimization. We already do those, and are serving our app off a CDN.
We've looked at creating static "snapshots" of these user pages, that we need to load very fast using something like react-static, and serving them as static versions and hydrating them later. Rewriting our project using something like next.js or gatsby is not an option as it would entail too much work. SSR is also not an option as our entire backend is coded in Django rather than Node.js
Are we on the right track? Is it possible / worth it to use react-static (or a similar tool) to do this? There is a LOT of documentation on how to create react-static projects from scratch but nothing on how to convert an existing project over, even if it's just a small subset of routes like we need.
Also, once the data changes on our user pages, how do we trigger a "rebuild" of the appropriate snapshot? Users don't update their data that often, about 3 of 4 times per month, but we have 3K users, so maybe 15 updates per hour would be the average. Can we trigger only a rebuild of the routes that actually changed?
Like you said, you could use react-static.
They have a feature which fills exactly with your need ( user's specific pages ).
In their example they use an array of posts to generate a specific static file for each of them.
This have a huge lesser amount of time taken to load, as it's only html static files.
Imagine having this scenario:
[
{
id: 'foo',
...
},
{
id: 'bar',
...
},
...
]
Following the example below this would generate something like this ( at runtime ):
- src
- pages
- blog
- posts
- foo // Specific post page
- bar // Specific post page
Look at into the example:
//static.config.js
export default {
// resolves an array of route objects
getRoutes: async () => {
// this is where you can make requests for data that will be needed for all
// routes or multiple routes - values returned can then be reused in route objects below
// ATTENTION: In here, instead of posts you'd fetch your users json data
const { data: posts } = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
return [
// route object
{
// React Static looks for files in src/pages (see plugins below) and matches them to path
path: "/blog",
// function that returns data for this specific route
getData: () => ({
posts
}),
// an array of children routes
// in this case we are mapping through the blog posts from the post variable above
// and setting a custom route for each one based off their post id
children: posts.map(post => ({
path: `/post/${post.id}`,
// location of template for child route
template: "src/containers/Post",
// passing the individual post data needed
getData: () => ({
post
})
}))
},
];
},
// basic template default plugins
plugins: [
[
require.resolve("react-static-plugin-source-filesystem"),
{
location: path.resolve("./src/pages")
}
],
require.resolve("react-static-plugin-reach-router"),
require.resolve("react-static-plugin-sitemap")
]
};
You can use a Service Worker.
Load the important fast pages as static, then in the background, using the Service worker load the longer resources.
You can also use a Service Worker for smart caching.
For example, the server can set a cookie with the current resource version (comes with that first page), and the Service worker can compare this to it’s resource version , and decide whether to loat it from cache or go to the server.

What is the best way to have a React-generated static (SEO) "public" frontend alongside a CRA "private" app?

I've been using Create-React-App and dig the whole setup, basically I'm looking to keep developing with JSX instead of switching to Gatsby/React-Static's Markdown/etc coding style. Similar question to this one regarding Gatsby. I'm looking to have a search engine optimized static "public" frontend (e.g. product pages, blog, etc.) that is generated by Gatsby or react-static. But I'd also like a typical client-side rendered React app for the "private" section (e.g. sellers can edit their product pages). I know you can set the "homepage" so I was thinking of setting the private section to "example.com/in/" or similar.
Any advice on how to best split this project?
If you aren't using the GraphQL stuff, Gatsby is predominantly just using React SSR and a custom webpack configuration with Reach Router and some glue to stick it all together.
You can absolutely have a separate Webpack configuration that outputs to your public folder and set up your host/deployment to route all of your non-static routes to your application entry.
You can also use Gatsby to generate all of those dynamic pages with juicy client-side fetches, and you basically get free static skeleton entry points for each of your pages like this:
const useMounted = () => {
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(true)
}, [])
return isMounted
}
const SomeComponent = props => {
const isMounted = useMounted()
return isMounted
? <div>Mounted client-side component</div>
: <SomeComponentSkeleton />
}
Effectively this just prevents issues with hydration that occur if you return null on server-side render and then return a component on the client-side. You can also use isMounted to kick off any code that uses window et al.

Next.js: Reduce data fetching and share data between pages

I'm looking for solutions for better data fetching in a Next.js app. In this question I'm not just looking for a solution, I'm looking for multiple options so we can look at the pros and cons.
The problem I have
Right now I have a few pages that all include a component that displays som static content and a that have some dynamic content that is fetched from an API. Each page do a fetch() in their getInitialProps() to get their own page data, but also the footer data, which is the same for all pages.
This of course works, but there is a lot of duplicated data fetching. The footer data will always be displayed for all pages and always be the same. It will also rarely be changed in the API, so no need for revalidate the data.
The answers I'm looking for
I'm not just looking to solve this one problem, I'm looking for an overview to learn some new practice for future projects as well. I like writing "obvious" code, so not looking for too hacky solutions, like writing to the window object etc. Simple solutions with less dependancies are preferred. The goal is a fast site. It's not that important to reduce network usage/API calls.
What I have thought so far
This is the possible solutions I've come up with, somewhat sorted from simple/obvious to more complex.
Do a fetch inside the Footer component (client side)
Do a fetch in getInitialProps (server side & client side) on all /pages
Do a fetch in _app.js with a HOC and hooking into it's getInitialProps() and add it to props, so data is available for all pages
Use zeit/swr and data prefetching to cache data
Use redux to store a global state
All of these "work", but most of them will refetch the data unnecessarily, and/or adds a bit more complexity. Here are the pros/cons as I see it (numbers are the same as above):
πŸ‘ Simple! Fetch code is only in one place, it's located where it's used. πŸ‘Ž Data is fetched after page is loaded, so the content "jumps" in to view. Data is refetched all the time.
πŸ‘ Simple! Data is fetched on the server, so content is available before the page is rendered. πŸ‘Ž Data is refetched for each page. We have to remember to fetch the same footer data for each page in their getInitialProps().
πŸ‘ We can do the fetch in one place and add it to all the pages props, so footer data is automatically available for all pages' props. πŸ‘Ž Might be a bit more complex for some to easily understand what's going on, as it requires a bit more understanding of how Next.js/React works. Still refetches the data for all pages. We now do two fetch() calls after each other (first in _app.js to load footer content, then in each page to get custom content), so it's even slower.
πŸ‘ Somewhat simple. We can use the prefetching to load data to cache even before the JS is loaded. After first page load, we will have fast data fetching. Can have fetch code directly in footer component. πŸ‘Ž The rel="preload" prefetching technique won't work with all types of fetching (for instance Sanity's client using groq). To not have "jumpy" content where the data is loaded after initial page load, we should provide useSWR() with initialData which still will require us to fetch data in getInitialProps(), but it would be enough to just do this on the server side. Could use the new getServerSideProps().
πŸ‘ We can load data once(?) and have it available throughout the application. Fast and less/no refetching. πŸ‘Ž Adds external dependency. More complex as you'll have to learn redux, even to just load one shared data object.
Current solution, using the solution described in bullet point number 2.
const HomePage = (props) => {
return (
<Layout data={props.footer}>
<Home data={props.page} />
</Layout>
)
}
// Not actual query, just sample
const query = `{
"page": *[_type == "page"][0],
"footer": *[_type == "footer"][0]
}`
HomePage.getInitialProps = async () => {
const data = await client.fetch(query)
return {
page: data.page
footer: data.footer
}
}
export default HomePage
Would love some more insight into this. I'm a missing something obvious?
O'right! I found this thread while I was looking for something else. But since I had to work on similar issues, I can give you some directions, and I will do my best to make it clear for you.
So there are some data which you want to have it share, across your app (pages/components).
Next.js uses the App component to initialize pages. You can override it and control the page initialization. to achieve that simply create _app.js file in root of pages directory. For more information follow this link: https://nextjs.org/docs/advanced-features/custom-app
Just like the way you can use getInitialProps in your pages to fetch data from your API, you can also use the same method in _app.js. So, I would fetch those data which I need to share them across my app and eliminate my API calls.
Well, Now I can think of two ways to share the data across my app
Using of createContext hooks.
1.1. Create a DataContext using createContext hooks. and wrap <Component {...pageProps} /> with your <DataContext.Provider>.
Here is a code snippet to give you a better clue:
<DataContext.Provider value={{ userData, footerData, etc... }}>
<Component {...pageProps} />
</DataContext.Provider>
1.2. Now in other pages/components you can access to your DataContext like following:
const { footerData } = useContext(DataContext);
And then you are able to do the manipulation in your front-end
populates props using getInitialProps
2.1. getInitialProps is used to asynchronously fetch some data, which then populates props. that would be the same case in _app.js.
The code in your _app.js would be something like this:
function MyApp({ Component, pageProps, footerData }) {
//do other stuffs
return (
<Component {...pageProps} footerData={footerData} />
;
}
MyApp.getInitialProps = async ({ Component, ctx }) => {
const footerRes = await fetch('http://API_URL');
const footerData = await footerRes.json();
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps, footerData };
};
2.2. Now in your pages (not in your components) you can access to props including those you have shared from _app.js
and you can start to do you manipulation.
Hope I could give you a clue and direction. Have fun exploring.

Resources