Next.js ISR pass additional data to getStaticProps from getStaticPaths - javascript

In SingleBlogPost.jsx i have:
export async function getStaticPaths() {
const res = await fetch("http://localhost:1337/api/posts");
let { data } = await res.json();
const paths = data.map((data) => ({
params: { slug: data.attributes.slug },
}));
return {
paths,
fallback: "blocking",
};
}
where I generate blog pages by their slug.
But then in getStaticProps I need to fetch single post by slug but I want to do it by id.
export async function getStaticProps(context) {
console.log("context", context);
const { slug } = context.params;
console.log("slug is:", slug);
const res = await fetch("http://localhost:1337/api/posts");
const { data } = await res.json();
return {
props: {
data,
},
revalidate: 10, // In seconds
};
}
And I want to keep url like /blog/:slug , I dont want to include id. in url .When I already fetch all posts in getStaticPaths how I can access post id in getStaticProps to avoid fetching by slug?

You can filter your API response by your slug to get the same result
const res = await fetch(`http://localhost:1337/api/posts?filters[slug][$eq]${slug}`);
This will generate your desired result

It looks like recently released a workaround using a file system cache.
The crux of the solution is that they save the body object in memory, using something like this:
this.cache = Object.create(null)
and creating methods to update and fetch data from the cache.
Discussion here: https://github.com/vercel/next.js/discussions/11272#discussioncomment-2257876
Example code:
https://github.com/vercel/examples/blob/main/build-output-api/serverless-functions/.vercel/output/functions/index.func/node_modules/y18n/index.js#L139:10

I found a concise work around that uses the object-hash package. I basically create a hash of the params object and use that to create the tmp filename both on set and get. The tmp file contains a json with the data I want to pass between the two infamous static callbacks.
The gist of it:
function setParamsData({params, data}) {
const hash = objectHash(params)
const tmpFile = `/tmp/${hash}.json`
fs.writeFileSync(tmpFile, JSON.stringify(data))
}
function getParamsData (context) {
const hash = objectHash(context.params)
const tmpFile = `/tmp/${hash}.json`
context.data = JSON.parse(fs.readFileSync(tmpFile))
return context
}
We can then use these helpers in the getStaticPaths and getStaticProps callbacks to pass data between them.
export function getStaticPaths(context) {
setParamsData({...context, data: {some: 'extra data'})
return {
paths: [],
fallback: false,
}
}
export function getStaticProps(context) {
context = getParamsData(context)
context.data // => {some: 'extra data'}
}
I'm sure someone can think of a nicer API then re-assigning a argument variable.
The tmp file creation is likely not OS independent enough and could use some improvement.

Related

How to pass additional parameter with dynamic routes in Reactjs

I am working in Reactjs and using nextjs,My [slug.js] is working fine with following url
<Link href={`/${post.slug}`}><a>
But i want to send/pass "hidden"(additional parameter) with this,whenever i try to do then i am getting 404 error,I want this because in some page i want to use different api in "serversideprops",Right now here is my code
export const getServerSideProps = async ({ params }) => {
console.log(params); // right now i am getting "slug" as parameter
if(params.anotherparamter)
{
//futher code
}
elseif(params.slug){
const { data: data2 } = await Axios.get(`https://xxxxxxxxxxxxxxxxxxxxxxxxx/${params.slug}`);
}
const blogs = data2;
return {
props: {
blogs: blogs
},
};
};
You can use the as prop to hide the query string.
Your link would look something like this
<Link href={`/${post.slug}?myparam="mysecret"`} as={`/${post.slug}`}></Link> //The link will not show the query param when redirected
You will then be able to access the myparam query in your serverSideProps like so.
export const getServerSideProps = async ({ params, query }) => {
...
const { myparam } = query
console.log(myparam) // will return mysecret as a string
You can read more from the docs

How to pass data in slug.js Reactjs

I am new in Nextjs, i am trying to integrate [slug.js] page, i want to know that how can we manage/get data in sidebar (similar blogs) ? in other words for blog details i used "get static path" and "props", But now i want to pass "current slug" ( to API) so i can fetch all blogs with this blog category,How can i do this ?
Client-side approach:
Since you pass the post as page-props via getStaticProps, you can either take the slug from there (if it's included in your data model), or extract the slug from the url via next's useRouter hook in case you want to do client-side fetching:
import axios from "axios"; // using axios as an example
import { useRouter } from "next/router";
const Component = () => {
const [similarPosts, setSimilarPosts] = useState([]);
const router = useRouter();
const { slug } = router.query;
const getSimilarPosts = async () => {
if (!router.isReady() || !slug) return [];
const { data } = await axios.get("/api/similar-posts-route/" + slug);
return data;
};
useEffect(() => {
if (similarPosts.length > 0) return;
(async () => {
const posts = await getSimilarPosts(); // assuming API returns an array of posts as data.
setSimilarPosts(posts);
})();
}, []);
return <div>Similar posts: {JSON.stringify(similarPosts)}</div>;
};
[...]
Server-Side approach (preferred):
I believe it would be a better approach to directly fetch similar posts inside getStaticProps to reduce API calls and for a better UX.
Inside getStaticProps you can take the slug from context.params and fetch all similar posts directly from your database/CMS, and pass them directly as props to your page component:
export async function getStaticProps({ params }) {
const { slug } = params;
// fetch similar posts directly from the database using the slug (don't call the API, it's not up yet during build phase)
const similarPosts = await executeDatabaseQueryForSimilarPosts(slug);
// [...] fetch the rest of the page props
return {
props: {
similarPosts,
// [...] return the rest of page props
},
revalidate: 60 * 30 // re-fetch the data at most every 30 minutes, so the posts stay up to date
};
}
// directly take all similar posts from props
const Component = ({similarPosts}) => {
return <div>Similar posts: {JSON.stringify(similarPosts)}</div>;
};

GraphQL - Cannot move resolver to a separate file

I have searched the forum for anything related to customizing GraphQL in Strapi v4 but found nothing.
Note: my GraphQL skills is a novice.
I extended my GraphQL resolver in Strapi v4, and it worked fine as long as the resolver was in the same file as "index.ts." I want to modularize my GraphQL code by moving the resolver into a separate file. When I did that, I kept getting the following error:
"resolvers" is defined in the resolver but not in the schema.
Below is my resolver embedded in the file index.ts, which works fine without any issue.
index.ts
/**
* Extend register for GraphQL
*/
register({ strapi }): void {
// customized programmatically using GraphQL's extension
const extensionService = strapi.plugin("graphql").service("extension");
const UID = "api::truth-lending-disclosure.truth-lending-disclosure";
extensionService.use(({ strapi }) => ({
typeDefs: ``,
resolvers: {
Query: {
truthLendingDisclosures: async (parent, args, context) => {
// toEntityResponse method to allow us to convert our response
// to the appropriate format before returning the data.
const { toEntityResponseCollection } = strapi
.plugin("graphql")
.service("format").returnTypes;
// define level to populate
let _populate = {
body: {
populate: {
section: true,
},
},
};
// using shadow CRUD from entity service to fetch data
let entities = await strapi.entityService.findMany(UID, {
populate: _populate,
});
// find and replace placeholder with key-value risCustomerTermDataMap
// return the result as JSON string
let stringResult = dataSubstitution(
JSON.stringify(entities), // convert an object to JSON string
risCustomerTermDataMap
);
// conver JSON string back object
let objectResult = JSON.parse(stringResult);
debugger;
return toEntityResponseCollection(objectResult);
},
},
},
}));
},
Moved the resolvers logic into a separate file mycustom.resolvers.ts
mycustom.resolvers.ts
import { risCustomerTermDataMap } from "../../../../libs/common/risaCustomerTermDataMap";
import { dataSubstitution } from "../../../../libs/helpers/dataSubstitution";
// the logic in this file does not work with index.ts yet
// keep getting "resolvers" define in resolvers, but not in schema.
export const resolvers = {
Query: {
truthLendingDisclosures: async (parent, args, context) => {
console.log("***** GraphQL Resolvers*****");
const UID = "api::truth-lending-disclosure.truth-lending-disclosure";
// toEntityResponse method to allow us to convert our response
// to the appropriate format before returning the data.
const { toEntityResponseCollection } = strapi
.plugin("graphql")
.service("format").returnTypes;
// define level to populate
let _populate = {
body: {
populate: {
section: true,
},
},
};
// using shadow CRUD from entity service to fetch data
let entities = await strapi.entityService.findMany(UID, {
populate: _populate,
});
// find and replace placeholder with key-value risCustomerTermDataMap
// return the result as JSON string
let stringResult = dataSubstitution(
JSON.stringify(entities), // convert an object to JSON string
risCustomerTermDataMap
);
// conver JSON string back object
let objectResult = JSON.parse(stringResult);
debugger;
return toEntityResponseCollection(objectResult);
},
},
};
What am I missing?
Finally,I got it to work.
I modified my resolver in the external file "mycustom.resolvers.ts" to this:
filename: mycustom.resolvers.ts
export const truthLendingDisclosureResolvers = {
Query: {
async truthLendingDisclosures(): Promise<any> {
console.log("***** external resolver *****");
const UID = "api::truth-lending-disclosure.truth-lending-disclosure";
// toEntityResponse method to allow us to convert our response
// to the appropriate format before returning the data.
const { toEntityResponseCollection } = strapi
.plugin("graphql")
.service("format").returnTypes;
// define level to populate
let _populate = {
body: {
populate: {
section: true,
},
},
};
// using shadow CRUD from entity service to fetch data
let entities = await strapi.entityService.findMany(UID, {
populate: _populate,
});
// find and replace placeholder with key-value risCustomerTermDataMap
// return the result as JSON string
let stringResult = dataSubstitution(
JSON.stringify(entities), // convert an object to JSON string
risCustomerTermDataMap
);
// conver JSON string back object
let objectResult = JSON.parse(stringResult);
return toEntityResponseCollection(objectResult, {
args: {},
resourceUID: UID,
});
},
},
};
and modified the "index.ts" to this:
filename: index.ts
register({ strapi }): void {
// customized programmatically using GraphQL's extension
const extensionService = strapi.plugin("graphql").service("extension");
const UID = "api::truth-lending-disclosure.truth-lending-disclosure";
// disable an action on a query
// extensionService.shadowCRUD(UID).disableAction("find");
extensionService.use(({ strapi }) => ({
typeDefs: ``,
resolvers: truthLendingDisclosureResolvers,
}));
},

How to fetch Strapi by slug, and how to populate the categories

I'm trying to fetch post for a react blog with strapi backend using the slug.
I created the custom route and custom controller, but the value returned is missing a few attributes like images and category.
When I fetch using post Id, I use query string to populate the object returned, but I don't know how to had qs to the slug API route.
Below is the custom controller, and the custom route
///custom controller
async findOne(ctx) {
const { slug } = ctx.params;
const { query } = ctx;
const entity = await strapi.service('api::article.article').findOne(slug, query);
const sanitizedEntity = await this.sanitizeOutput(entity, query);
return this.transformResponse(sanitizedEntity);
}
///Custom Route
{
method: 'GET',
path: '/articles/slug/:slug',
handler: 'custom-controller.findOne',
config: {
policies: []
},
This is how I fetch from client in useEffect
useEffect(()=>{
const fetchData = async()=>{
// const query = qs.stringify({
// populate: '*',
// }, {
// encodeValuesOnly: true,
// });
const res = await axios.get(`http://localhost:1337/api/articles?filters[slug][$eq]=${slug}`)
console.log(res.data)
updateState(res.data)
}
fetchData()
setLoading(false)
}, [slug])
I've also tried to use the Entity API Service, but I just couldn't get it to work.
How do I populate the object to include these missing attributes?
With Strapi v4 you can do it this way
1. Create a file in src/api/article/_custom.js
Please note I put an underscore because:
Routes files are loaded in alphabetical order. To load custom routes before core routes, make sure to name custom routes appropriately (e.g. 01-custom-routes.js and 02-core-routes.js).
Source: https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#creating-custom-routers
module.exports = {
routes: [
{
method: 'GET',
path: '/articles/:slug',
handler: 'article.findOne',
config: {
auth: false
},
}
]
}
2. Edit the src/api/article/controllers/article.js
'use strict';
/**
* article controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::article.article', ({ strapi }) => ({
// Query by slug
async findOne(ctx) {
// thanks to the custom route we have now a slug variable
// instead of the default id
const { slug } = ctx.params;
const entity = await strapi.db.query('api::article.article').findOne({
where: { slug }
});
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return this.transformResponse(sanitizedEntity);
},
}));
Now you can call your api this way:
http://localhost:1337/api/articles/my-beautiful-article-about-orange
Reference: https://www.youtube.com/watch?v=OVV0CfgX6Qk
Note: In the video, custom.js is loaded before post.js ^^

How to use window in getStaticProps?

I want to load a specific configuration (opening hours, company address,…) based on the subdomain. Thus I want to run a single instance of NextJS for different clients (each client has a subdomain).
I try
export async function getStaticProps() {
const subdomain = /:\/\/([^\/?]+)/.exec(window.location.href)[1].split(".")[0];
const config = mysqlquery(subdomain);
return {
props: {
config,
}
}
}
and I get window is not defined.
First install absoluteUrl then you should request to your local api to find subdomain let see the code:
export async function getStaticProps() {
const req = await
fetch("http://localhost:3000/api/hello");
const dd = await req.json();
console.log("sub", dd);
return {
props: {
aboutData: [],
},
};
}
now in pages/api/hello.js
import absoluteUrl from "next-absolute-url";
export default async function
handler(req,res) {
const { origin } = absoluteUrl(req);
let subdomain = origin.match(/\w+/);
console.log("request ", subdomain);
return res.status(200).json({ subDomain:
subdomain });
}
it will send an array subdomain is the first index of array actually i am not sure about regex that i write you can check if not work write your own regex to determin the subdomain
You can access the url using context.req.headers.referer, being your code as follows:
export async function getStaticProps(context) {
const subdomain = /:\/\/([^\/?]+)/.exec(context.req.headers.referer).split(".")[0];
const config = mysqlquery(subdomain);
return {
props: {
config,
}
}
}

Categories