Consider the following code within gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-fetch`,
options: {
name: `brands`,
type: `brands`,
url: `${dynamicURL}`, // This is the part I need to be dynamic at run/build time.
method: `get`,
axiosConfig: {
headers: { Accept: "text/csv" },
},
saveTo: `${__dirname}/src/data/brands-summary.csv`,
createNodes: false,
},
},
],
}
As you can see above, the URL for the source plugin is something that I need to be dynamic. The reason for this is that the file URL will change every time it's updated in the CMS. I need to query the CMS for that field and get its CDN URL before passing to the plugin.
I tried adding the following to the top of gatsby-config.js but I'm getting errors.
const axios = require("axios")
let dynamicURL = ""
const getBrands = async () => {
return await axios({
method: "get",
url: "https://some-proxy-url-that-returns-json-with-the-csv-file-url",
})
}
;(async () => {
const brands = await getBrands()
dynamicURL = brands.data.summary.url
})()
I'm assuming this doesn't work because the config is not waiting for the request above to resolve and therefore, all we get is a blank URL.
Is there any better way to do this? I can't simply supply the source plugin with a fixed/known URL ahead of time.
Any help greatly appreciated. I'm normally a Vue.js guy but having to work with React/Gatsby and so I'm not entirely familiar with it.
I had similar requirement where I need to set siteId of gatsby-plugin-matomo dynamically by fetching data from async api. After searching a lot of documentation of gatsby build lifecycle, I found a solution.
Here is my approach -
gatsby-config.js
module.exports = {
siteMetadata: {
...
},
plugins: {
{
resolve: 'gatsby-plugin-matomo',
options: {
siteId: '',
matomoUrl: 'MATOMO_URL',
siteUrl: 'GATSBY_SITE_URL',
dev: true
}
}
}
};
Here siteId is blank because I need to put it dynamically.
gatsby-node.js
exports.onPreInit = async ({ actions, store }) => {
const { setPluginStatus } = actions;
const state = store.getState();
const plugin = state.flattenedPlugins.find(plugin => plugin.name === "gatsby-plugin-matomo");
if (plugin) {
const matomo_site_id = await fetchMatomoSiteId('API_ENDPOINT_URL');
plugin.pluginOptions = {...plugin.pluginOptions, ...{ siteId: matomo_site_id }};
setPluginStatus({ pluginOptions: plugin.pluginOptions }, plugin);
}
};
exports.createPages = async function createPages({ actions, graphql }) {
/* Create page code */
};
onPreInit is a gatsby lifecycle method which is executing just after plugin loaded from config. onPreInit lifecycle hook has some built in methods.
store is the redux store where gatsby is storing all required information for build process.
setPluginStatus is a redux action by which plugin data can be modified in redux store of gatsby.
Here the important thing is onPreInit lifecycle hook has to be called in async way.
Hope this helps someone in future.
Another approach that may work for you is using environment variables as you said, the URL is known so, you can add them in a .env file rather than a CSV.
By default, Gatsby uses .env.development for gatsby develop and a .env.production for gatsby build command. So you will need to create two files in the root of your project.
In your .env (both and .env.development and .env.production) just add:
DYNAMIC_URL: https://yourUrl.com
Since your gatsby-config.js is rendered in your Node server, you don't need to prefix them by GATSBY_ as the ones rendered in the client-side needs. So, in your gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-fetch`,
options: {
name: `brands`,
type: `brands`,
url: process.env.DYNAMIC_URL, // This is the part I need to be dynamic at run/build time.
method: `get`,
axiosConfig: {
headers: { Accept: "text/csv" },
},
saveTo: `${__dirname}/src/data/brands-summary.csv`,
createNodes: false,
},
},
],
It's important to avoid tracking those files in your Git repository since you don't want to expose this type of data.
Related
I would like to obtain all (or a subset) of my records from an Algolia index and access them via GraphQL.
I know there is a Gatsby plugin that allows you to do the opposite i.e., add data from a GraphQL query to Algolia, but not the other way around.
I have been able to get the tutorial for adding GraphQL data to work, but I have not had any success when trying to go beyond hardcoded arrays (this is in the gatsby-node.js file):
const algoliasearch = require("algoliasearch/lite");
const searchClient = algoliasearch(
process.env.GATSBY_ALGOLIA_APP_ID,
process.env.GATSBY_ALGOLIA_SEARCH_KEY
)
const searchIndex = searchClient.initIndex(process.env.GATSBY_ALGOLIA_INDEX_NAME)
exports.sourceNodes = ({ actions, createNodeId, createContentDigest }) => {
searchIndex.search("", {
attributesToRetrieve: ["name", "url"]
}).then(({ hits }) => {
hits.forEach(hit => {
const node = {
name: hit.name,
url: hit.url,
id: createNodeId(`hit-${hit.name}`),
internal: {
type: "hit",
contentDigest: createContentDigest(hit),
},
}
actions.createNode(hit)
})
});
}
While the console successfully logs the array of nodes, and the verbose Gatsby deploy output includes the "hit" node as a node type, they do not appear in the GraphQL explorer.
Any help is greatly appreciated, thank you!
I have some queries from an API-Server that returns a json object that will be static over a user session, but not static forever.
It's a one-pager with Vue router.
How can I achieve that I:
can access this.myGlobals (or similar eg window.myGlobals) in all components, where my prefetched json-data from API-Server is stored.
My approach that is already working is to embed help.js via a mixin.
Oddly enough, I get hundreds of calls to this query. At first I thought that it only happened in the frontend and is chached, but the requests are actually sent hundreds of times to the server. I think it is a mistake of my thinking, or a systematic mistake.
i think the problem is, that the helper.js is not static living on the vue instance
main.js:
import helpers from './helpers'
Vue.mixin(helpers)
helpers.js:
export default {
data: function () {
return {
globals: {},
}
}, methods: {
//some global helper funktions
},
}, mounted() {
let url1 = window.datahost + "/myDataToStore"
this.$http.get(url1).then(response => {
console.log("call")
this.globals.myData = response.data
});
}
}
log in console:
call
SomeOtherStuff
(31) call
SomeOtherStuff
(2) call
....
log on server:
call
call
call (pew pew)
My next idea would be to learn vuex, but since its a easy problem, im not sure if i really need that bomb ?
You can use plugin to achieve this.
// my-plugin.js
export default {
install (Vue, options) {
// start fetching data right after install
let url1 = window.datahost + "/myDataToStore"
let myData
Vue.$http.get(url1).then(response => {
console.log("call")
myData = response.data
})
// inject via global mixin
Vue.mixin({
computed: {
myData () {
return myData
}
}
})
// or inject via instance property
Vue.prototype.$myData = myData
// or if you want to wait until myData is available
Vue.prototype.$myData = Vue.$http.get(url1)
.then(response => {
console.log("call")
myData = response.data
})
}
}
and use it:
Vue.use(VueResource)
Vue.use(myPlugin)
I am writing test case for a react chat application, we are using amazon-connect-chatjs library for that so this library providing few apis like session.onConnectionEstablished and session.onMessage so on, in this apis we are writing few logic based on that component will render accordingly.
so I want to mock for example session.onConnectionEstablished and pass inside that method or api so it pass through our logic that will render particular component and I will write test cases for that component
which rendered , Any idea how to do this. please see the logic below
import 'amazon-connect-chatjs'
const chat = ()=>{
const [userName setUserName] = useState('')
/* initial setup to initiate amazon-connect-chatjs */
connect.ChatSession.setGlobalConfig({
loggerConfig: {
logger: {
debug: (msg) => console.debug(msg),
info: (msg) => console.info(msg),
warn: (msg) => console.warn(msg),
error: (msg) => console.error(msg)
},
level: connect.ChatSession.LogLevel.WARN,
},
region: "us-east-1",
});
const customerChatSession = connect.ChatSession.create({
chatDetails: { // REQUIRED
contactId: "...", // REQUIRED
participantId: "...", // REQUIRED
participantToken: "...", // REQUIRED
},
options: {
region: "us-east-1",
`connect.ChatSession.setGlobalConfig()`
},
type: connect.ChatSession.SessionTypes.CUSTOMER, // REQUIRED
});
customerChatSession.create().then((response)=>{
return response;
}
)
customerChatSession.onConnectionEstablished(()=>{
setLoaderMessage('connecting...')
setUserName('true')
})
{{userName && <MessageList>}}
}
Note: by giving mock data I can pass required details to chatjs library and I getting response from customerChatSession.create().then((response)=>{ return response; }
but after that I kind of stuck how to mock
customerChatSession.onConnectionEstablished and pass through that method if it pass, it sets the useState which will render the component so i can test whether the component is rendering or not
I am not sure whether mocking is write approach or any other way to do this
Thanks
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.
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.