I'm currently working on a site wgich has a frontend consisting of next.js which communicates over graphql to a WordPress backend. the persistent queries are generated(by graphql-codegen) and are saved to a .json files both the backend and frontend can access these files.
here is the code relating to apollo and apollo codegen
codegen.js
const environment = process.env.ENV || 'development';
const url = require(`./config/${environment}`);
module.exports = {
schema: `${url.backend}/graphql`,
documents: ['./lib/queries/fragments/*.js', './lib/queries/*.js'],
generates: {
'./lib/persisted-query-ids/client.json': [
{
'graphql-codegen-persisted-query-ids': {
output: 'client',
algorithm: 'sha256',
preset: 'near-operation-file',
},
},
],
'./lib/persisted-query-ids/server.json': [
{
'graphql-codegen-persisted-query-ids': {
output: 'server',
algorithm: 'sha256',
preset: 'near-operation-file',
},
},
],
},
};
createApolloClient.js
export default function createApolloClient(initialState, ctx) {
const hashes = require('../../persisted-query-ids/client.json');
const persistedLink = createPersistedQueryLink({
useGETForHashedQueries: true, // Optional but allows better caching
// eslint-disable-next-line react-hooks/rules-of-hooks
generateHash: usePregeneratedHashes(hashes),
});
// The `ctx` (NextPageContext) will only be present on the server.
// use it to extract auth headers (ctx.req) or similar.
const fetchOptions = {
// credentials: 'include',
redirect: 'manual',
};
const fetchWithCookies = async (input, init) => {
let isomorphicFetch;
if (typeof window === 'undefined') {
isomorphicFetch = (await import('isomorphic-unfetch')).default;
} else {
isomorphicFetch = window.fetch;
}
const result = await isomorphicFetch(input, init);
if (ctx?.res) {
const cookiesFromApi = result.headers.get('set-cookie');
if (cookiesFromApi) {
ctx?.res.setHeader('set-cookie', cookiesFromApi);
}
}
return result;
};
const cookieLink = createCookieLink(ctx);
const httpLink = persistedLink.concat(
new HttpLink({
uri: `${config.wordpress.backend}/graphql`,
fetchOptions,
fetch: fetchWithCookies,
useGETForQueries: true,
})
);
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: cookieLink.concat(httpLink),
cache: new InMemoryCache().restore(initialState || {}),
});
}
this is the URL of the request, as you can see its quite big. the problem is apollo wants to use the query and not the persisted query id. this request causes a http 414 code. one of the fixes is to make another query for the fragments
https://example.com/wp/graphql?query=query%20GetFrontPage%20%7B%0A%20%20page%3A%20pageOnFront%20%7B%0A%20%20%20%20nodes%20%7B%0A%20%20%20%20%20%20...PageData%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D%0A%0Afragment%20PageData%20on%20Page%20%7B%0A%20%20title%0A%20%20content%0A%20%20link%0A%20%20excerpt%0A%20%20pageTemplate%0A%20%20hero%20%7B%0A%20%20%20%20heroColor%0A%20%20%20%20heroImage%20%7B%0A%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20altText%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20heroTitle%0A%20%20%20%20heroTitleTop%0A%20%20%20%20heroDescription%0A%20%20%20%20__typename%0A%20%20%7D%0A%20%20featuredImage%20%7B%0A%20%20%20%20...FeaturedImage%0A%20%20%20%20__typename%0A%20%20%7D%0A%20%20seo%20%7B%0A%20%20%20%20...Seo%0A%20%20%20%20__typename%0A%20%20%7D%0A%20%20...FlexContentPage%0A%20%20__typename%0A%7D%0A%0Afragment%20Seo%20on%20PostTypeSEO%20%7B%0A%20%20title%0A%20%20metaDesc%0A%20%20metaKeywords%0A%20%20metaRobotsNofollow%0A%20%20metaRobotsNoindex%0A%20%20canonical%0A%20%20opengraphTitle%0A%20%20opengraphDescription%0A%20%20opengraphImage%20%7B%0A%20%20%20%20sourceUrl(size%3A%20FEATIMGSOCIAL)%0A%20%20%20%20__typename%0A%20%20%7D%0A%20%20twitterTitle%0A%20%20twitterDescription%0A%20%20twitterImage%20%7B%0A%20%20%20%20sourceUrl(size%3A%20FEATIMGSOCIAL)%0A%20%20%20%20__typename%0A%20%20%7D%0A%20%20__typename%0A%7D%0A%0Afragment%20FlexContentPage%20on%20Page%20%7B%0A%20%20flexContent%20%7B%0A%20%20%20%20flexiblecontent%20%7B%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_SellingPoints%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20subtitle%0A%20%20%20%20%20%20%20%20sellingpointsBackgroundColor%0A%20%20%20%20%20%20%20%20cta%20%7B%0A%20%20%20%20%20%20%20%20%20%20target%0A%20%20%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20%20%20url%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20list%20%7B%0A%20%20%20%20%20%20%20%20%20%20listTitle%0A%20%20%20%20%20%20%20%20%20%20pointsList%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20point%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Titlelayout%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20titleBackgroundcolor%0A%20%20%20%20%20%20%20%20titles%20%7B%0A%20%20%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20%20%20titleTitle%0A%20%20%20%20%20%20%20%20%20%20titleTitleColor%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Testemonial%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20testemonial%0A%20%20%20%20%20%20%20%20facts%20%7B%0A%20%20%20%20%20%20%20%20%20%20factCount%0A%20%20%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20%20%20factTitle%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Pointslayout%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20points%20%7B%0A%20%20%20%20%20%20%20%20%20%20itemdescription%0A%20%20%20%20%20%20%20%20%20%20itemtitle%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Bloglayout%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20blogSubtitle%0A%20%20%20%20%20%20%20%20blogCta%20%7B%0A%20%20%20%20%20%20%20%20%20%20target%0A%20%20%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20%20%20url%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20blogCases%20%7B%0A%20%20%20%20%20%20%20%20%20%20...%20on%20Post%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20%20%20%20%20uri%0A%20%20%20%20%20%20%20%20%20%20%20%20details%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20broad%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20customer%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20normal%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20small%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20subtitle%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20video%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mediaItemUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20slug%0A%20%20%20%20%20%20%20%20%20%20%20%20featuredImage%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20node%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20blogTitle%0A%20%20%20%20%20%20%20%20blogColor%0A%20%20%20%20%20%20%20%20blogSubtitle%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Latestlayout%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20latestTitle%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Testlayout%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20testTitle%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Textlayout%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20wysiwyg%0A%20%20%20%20%20%20%20%20backgroundcolor%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Gallerylayout%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20photos%20%7B%0A%20%20%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20%20%20pullup%0A%20%20%20%20%20%20%20%20%20%20photo%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Visuallayout%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20image%20%7B%0A%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Customerslayout%20%7B%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20backgroundColor%0A%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20subtitle%0A%20%20%20%20%20%20%20%20customers%20%7B%0A%20%20%20%20%20%20%20%20%20%20link%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20target%0A%20%20%20%20%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20%20%20%20%20url%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20logo%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Slidelayout%20%7B%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20ctaLink%20%7B%0A%20%20%20%20%20%20%20%20%20%20target%0A%20%20%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20%20%20url%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20slideBigTitle%0A%20%20%20%20%20%20%20%20slideBigDescription%0A%20%20%20%20%20%20%20%20slideContent%20%7B%0A%20%20%20%20%20%20%20%20%20%20slideImage%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20slideColor%0A%20%20%20%20%20%20%20%20%20%20slideTitle%0A%20%20%20%20%20%20%20%20%20%20slideDescription%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Teamlayout%20%7B%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20teamContent%20%7B%0A%20%20%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20%20%20teamMemberName%0A%20%20%20%20%20%20%20%20%20%20teamMemberRole%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20teamBigTitle%0A%20%20%20%20%20%20%20%20teamBigSubtitle%0A%20%20%20%20%20%20%20%20teamContent%20%7B%0A%20%20%20%20%20%20%20%20%20%20teamMemberName%0A%20%20%20%20%20%20%20%20%20%20teamMemberRole%0A%20%20%20%20%20%20%20%20%20%20teamMemberImage%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Awardslayout%20%7B%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20awardsTitle%0A%20%20%20%20%20%20%20%20awardsBackgroundColor%0A%20%20%20%20%20%20%20%20awardsSubtitle%0A%20%20%20%20%20%20%20%20awards%20%7B%0A%20%20%20%20%20%20%20%20%20%20awardLink%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20target%0A%20%20%20%20%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20%20%20%20%20url%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20awardLogo%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20awardImage%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sourceUrl%0A%20%20%20%20%20%20%20%20%20%20%20%20altText%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20awardPrize%0A%20%20%20%20%20%20%20%20%20%20awardProject%0A%20%20%20%20%20%20%20%20%20%20awardOrganisation%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20...%20on%20Page_Flexcontent_Flexiblecontent_Accordionlayout%20%7B%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20fieldGroupName%0A%20%20%20%20%20%20%20%20accordionBackgroundColor%0A%20%20%20%20%20%20%20%20accordionItems%20%7B%0A%20%20%20%20%20%20%20%20%20%20accordionTitle%0A%20%20%20%20%20%20%20%20%20%20accordionContent%0A%20%20%20%20%20%20%20%20%20%20accordionServices%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20accordionService%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20__typename%0A%20%20%7D%0A%20%20__typename%0A%7D%0A%0Afragment%20FeaturedImage%20on%20NodeWithFeaturedImageToMediaItemConnectionEdge%20%7B%0A%20%20node%20%7B%0A%20%20%20%20link%0A%20%20%20%20sourceUrl(size%3A%20FEATIMGSOCIAL)%0A%20%20%20%20altText%0A%20%20%20%20title%0A%20%20%20%20mediaDetails%20%7B%0A%20%20%20%20%20%20width%0A%20%20%20%20%20%20height%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20__typename%0A%20%20%7D%0A%20%20__typename%0A%7D&operationName=GetFrontPage&variables=%7B%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22e429ca4a5a03ff00fd8e895c639f032e66a3b212ced449b733d8a38693458419%22%7D%7D
What I want is that apollo automatically uses queryId as seen on but I just can't figure out what could lead to a solution. if you have any suggestions I would love to hear them.
it looks like the only solution is to split up the query into multible parts
Related
Apollo client fails to do query when initialized with SchemaLink (nexus) instead of HttpLink. I get this error Cannot set properties of undefined (setting 'select')
function createIsomorphLink() {
if (typeof window === 'undefined') {
const { SchemaLink } = require('#apollo/client/link/schema')
const { schema } = require('./nexusSchema')
return new SchemaLink({ schema })
} else {
const { HttpLink } = require('#apollo/client/link/http')
return new HttpLink({
uri: '/api/graphql',
credentials: 'same-origin',
})
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: from([errorLink, createIsomorphLink()]),
cache: new InMemoryCache(),
})
}
Here's my getServerSideProps. I initialize apollo client and use apolloClient.query(...)
export const getServerSideProps = async ({ params }) => {
const id = params.id
const apolloClient = initializeApollo()
// Here's where the error occurs
const { data } = await apolloClient.query({
query: gql`
query findUniquePage($id: String!) {
findUniquePage(where: { id: $id }) {
title
type
content
isPublished
}
}
`,
variables: { id },
})
// ...snippet..
}
I tried creating simple 'hello' query with string response, so that there was no select part. But error stayed the same.
export const getServerSideProps = async () => {
const apolloClient = initializeApollo()
const { data } = await apolloClient.query({
query: gql`query hello { hello }`,
})
return {
props: {},
}
}
My apollo client version is 3.7.0. The latest is 3.7.1 but by CHANGELOG there seems to be nothing that addresses my issue. Probably, the problem is how apollo client requests my api via SchemaLink, or how my api parses that request (apollo server + code first nexus). Because when I replaced SchemaLink with HttpLink, code worked fine. But I kinda want to keep the SchemaLink. Any advice?
I am storing my token in a httpOnly cookie, but when I want to built a HOC for guarding the routes there is no way to access the cookie directly inside a component, I have to do it inside the server side,
I tried to do something like this but it doesn't work:
import Cookie from "cookies";
const withAuth = (Page) => {
Page.getServerSideProps = async ({ req, res }) => {
const cookie = new Cookie(req, res);
const token = cookie.get("token");
if (!token)
return {
redirect: {
permanent: false,
destination: "/login",
},
};
return {
props: {
token,
},
};
};
return Page;
};
export default withAuth;
The getServerSideProps function only works in pages, not components.
The following snippet should help you create a HOC for authentication. This example uses the concepts of closures. I'll call this one withAdministrator.jsx.
// withAdministrator.jsx
export default (GetServerSidePropsFunction) => async (ctx) => {
// 1. Check if there is a token.
const token = ctx.req.cookies?.jwt || null;
// 2. Perform an authorized HTTP GET request to the private API to get user data.
// In here, assume that 'getAuth' is a function to perform authorized GET request using the token value in the 'Authorization' header.
const { data } = await getAuth(`${process.env.PRIVATE_API_URL}/api/v1/users/user`, token);
// 3. If there is no user, or the user is not an admin, then redirect to unauthorized.
if (!data || data.role !== 'admin') {
return {
redirect: {
destination: '/unauthorized',
permanent: false,
},
};
}
// 4. Return via closure: 'GetServerSidePropsFunction'.
return await GetServerSidePropsFunction(ctx);
};
You'll call it like this. Let's say you want to access the /admin route.
export const getServerSideProps = withAdministrator(() => {
return {
props: {},
};
});
const Admin = () => {
return (
<YourComponent />
);
};
You can do anything you want inside the returned function. For example, you might want to fetch data after authenticating the user.
Further reading: Data fetching in Next.js.
I am attempting to implement NextAuth in my NextJs app. I am following the official documentation. But for one reason or the other, it seems like the user session object is not generated on login.
Here is my code from my pages/api/auth/[...nextauth].js file
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
import axios from "axios";
export default (req, res) =>
NextAuth(req, res, {
providers: [
Providers.Credentials({
id: 'app-login',
name: APP
authorize: async (credentials) => {
console.log("credentials_:", credentials);
try {
const data = {
username: credentials.username,
password: credentials.password
}
// API call associated with authentification
// look up the user from the credentials supplied
const user = await login(data);
if (user) {
// Any object returned will be saved in `user` property of the JWT
return Promise.resolve(user);
}
} catch (error) {
if (error.response) {
console.log(error.response);
Promise.reject(new Error('Invalid Username and Password combination'));
}
}
},
}),
],
site: process.env.NEXTAUTH_URL || "http://localhost:3000",
session: {
// Use JSON Web Tokens for session instead of database sessions.
// This option can be used with or without a database for users/accounts.
// Note: `jwt` is automatically set to `true` if no database is specified.
jwt: true,
// Seconds - How long until an idle session expires and is no longer valid.
maxAge: 1 * 3 * 60 * 60, // 3 hrs
// Seconds - Throttle how frequently to write to database to extend a session.
// Use it to limit write operations. Set to 0 to always update the database.
// Note: This option is ignored if using JSON Web Tokens
updateAge: 24 * 60 * 60, // 24 hours
},
callbacks: {
// signIn: async (user, account, profile) => { return Promise.resolve(true) },
// redirect: async (url, baseUrl) => { return Promise.resolve(baseUrl) },
// session: async (session, user) => { return Promise.resolve(session) },
// jwt: async (token, user, account, profile, isNewUser) => { return Promise.resolve(token) }
},
pages: {
signIn: '/auth/credentials-signin',
signOut: '/auth/credentials-signin?logout=true',
error: '/auth/credentials-signin', // Error code passed in query string as ?error=
newUser:'/'
},
debug: process.env.NODE_ENV === "development",
secret: process.env.NEXT_PUBLIC_AUTH_SECRET,
jwt: {
secret: process.env.NEXT_PUBLIC_JWT_SECRET,
}
});
const login = async data => {
var config = {
headers: {
'Content-Type': "application/json; charset=utf-8",
'corsOrigin': '*',
"Access-Control-Allow-Origin": "*"
}
};
const url = remote_user_url;
const result = await axios.post(url, data, config);
console.log('result', result);
return result;
};
What am I not getting it right here? Thanks for the help.
I managed to resolve the issue eventually. Something was wrong due to specifying the 'id' and 'name' options for the custom credential provider
I have removed them and the code is working now.
I'm trying to deploy an app using Prismic as CMS and everything works perfectly locally, but once I deploy to vercel I get the error:
19:09:51.850 | TypeError: Cannot read property 'titulo_categoria' of undefined
There seems to be something wrong when it tries to get the data from Prismic.
My code is the following:
import {getAllCategorias, getCategory2} from '../../lib/api';
export default function Index({cat}) {
return <>{cat.titulo_categoria[0].text}</>;
}
export async function getStaticProps({params}) {
const data = await getCategory2(params.slug);
return {
props: {
cat: data?.categorias ?? null,
},
};
}
export async function getStaticPaths() {
const allPosts = await getAllCategorias();
return {
paths: allPosts?.map(({node}) => `/test/${node._meta.uid}`) || [],
fallback: true,
};
}
And the API code that gets data from Prismic is:
import Prismic from 'prismic-javascript';
const REPOSITORY = process.env.PRISMIC_REPOSITORY_NAME;
const REF_API_URL = `https://${REPOSITORY}.prismic.io/api/v2`;
const GRAPHQL_API_URL = `https://${REPOSITORY}.prismic.io/graphql`;
// export const API_URL = 'https://your-repo-name.cdn.prismic.io/api/v2'
export const API_TOKEN = process.env.PRISMIC_API_TOKEN;
export const API_LOCALE = process.env.PRISMIC_REPOSITORY_LOCALE;
export const PrismicClient = Prismic.client(REF_API_URL, {
accessToken: API_TOKEN,
});
async function fetchAPI(query, {previewData, variables} = {}) {
const prismicAPI = await PrismicClient.getApi();
const res = await fetch(
`${GRAPHQL_API_URL}?query=${query}&variables=${JSON.stringify(variables)}`,
{
headers: {
'Prismic-Ref': previewData?.ref || prismicAPI.masterRef.ref,
'Content-Type': 'application/json',
'Accept-Language': API_LOCALE,
Authorization: `Token ${API_TOKEN}`,
},
}
);
if (res.status !== 200) {
console.log(await res.text());
throw new Error('Failed to fetch API');
}
const json = await res.json();
if (json.errors) {
console.error(json.errors);
throw new Error('Failed to fetch API');
}
return json.data;
}
export async function getCategory2(slug) {
const data = await fetchAPI(
`
query CategoryBySlug($slug: String!, $lang: String!) {
categorias(uid: $slug, lang: $lang) {
titulo_categoria
_meta {
uid
}
}
}
`,
{
variables: {
slug,
lang: API_LOCALE,
},
}
);
return data;
}
Any idea what's wrong with this? I been trying to figure it out for the whole day without any luck
Perhaps you already checked that, but since you mentioned everything works locally and not on Vercel are you sure your environment variables are set there? Especially PRISMIC_API_TOKEN since it appears you're relying on it to query the API?
Also I'm a bit worried about that part of the code:
props: {
cat: data?.categorias ?? null,
}
...where you might be sending a null value to your Index component resulting in your error, I'd try that instead:
props: {
cat: data?.categorias ?? {},
}
...plus using the safe navigation operator (?.) on the Index component?
Let me know how it goes!
I usually find a solution to a problem with Google/Stackoverflow/Meteor forums or find someone to fix it. However, this time I’m completely stuck. Here is my issue:
I’ve created a field service management app with Meteor/React/React Native. Managers can create tasks/work orders on the web app, the field team can create reports/work logs on the mobile app.
However, the region I’m in has a poor internet connection in some areas. So the offline feature is essential for both viewing tasks/reports but also creating the reports (not the tasks). To solve the offline data access, I’ve followed Spencer Carli’s excellent tutorial https://hackernoon.com/offline-first-react-native-meteor-apps-2bee8e976ec7
It’s working very well.
Things go wrong for creating offline reports. Basically, I have created an action queue in redux store to handle offline reports creation. When connection is back online, actions are being mapped, reports are created on the server, actions are then deleted, and offline created reports are being deleted from mini mongo and redux because anyway, once created on the server, it’s auto synced again.
It’s working very well BUT sometimes, especially when internet connection is slow, duplicates are created. And like, 50+ duplicates of the same report sometimes.
Here is the action queue syncing:
async handleSync(props) {
const data = Meteor.getData();
const db = data && data.db;
if (props.loading === false && props.connectionStatus === 'connected' && Meteor.userId() && db && props.actionQueue && props.actionQueue.length > 0) {
for (let action of props.actionQueue) {
if (action.msg === 'createReport') {
const report = {
organizationId: action.doc.organizationId,
taskId: action.doc.taskId,
status: action.doc.status,
startedAt: action.doc.startedAt,
};
const result = await Meteor.call('Reports.create', report, (error, res) => {
if (error) {
Alert.alert(I18n.t('main.error'), `${I18n.t('main.errorSyncReport')} ${error.reason}`);
} else {
props.dispatch({ type: 'REMOVE_ACTION_QUEUE', payload: action.actionId });
props.dispatch({ type: 'REMOVE_OFFLINE_REPORT', payload: action.doc._id });
db['reports'].del(action.doc._id);
const task = {
organizationId: action.doc.organizationId,
taskId: action.doc.taskId,
};
Meteor.call('Tasks.updateTaskStatus', task);
}
});
return result;
}
else if (action.msg === 'completeReport') {
// this action is for completion of reports that have been created online
const report = {
organizationId: action.doc.organizationId,
reportId: action.doc._id,
comments: action.doc.comments,
isTaskCompleted: action.doc.isTaskCompleted,
completedAt: action.doc.completedAt,
fields: action.doc.fields,
};
const result = await Meteor.call('Reports.completeReport', report, (error, res) => {
if (error) {
Alert.alert(I18n.t('main.error'), `${I18n.t('main.errorSyncReport')} ${error.reason}`);
} else {
props.dispatch({ type: 'REMOVE_ACTION_QUEUE', payload: action.actionId });
const task = {
organizationId: action.doc.organizationId,
taskId: action.doc.taskId,
};
Meteor.call('Tasks.updateTaskStatus', task);
}
});
return result;
}
else if (action.msg === 'createCompletedReport') {
// this action is for completion of reports that have been created offline to avoid _id problems
// so a new completed report is created and the offline report is deleted
const report = {
organizationId: action.doc.organizationId,
taskId: action.doc.taskId,
comments: action.doc.comments,
isTaskCompleted: action.doc.isTaskCompleted,
fields: action.doc.fields,
status: action.doc.status,
startedAt: action.doc.startedAt,
completedAt: action.doc.completedAt,
};
const result = await Meteor.call('Reports.create', report, (error, res) => {
if (error) {
Alert.alert(I18n.t('main.error'), `${I18n.t('main.errorSyncReport')} ${error.reason}`);
} else {
props.dispatch({ type: 'REMOVE_ACTION_QUEUE', payload: action.actionId });
props.dispatch({ type: 'REMOVE_OFFLINE_REPORT', payload: action.doc._id });
db['reports'].del(action.doc._id);
const task = {
organizationId: action.doc.organizationId,
taskId: action.doc.taskId,
};
Meteor.call('Tasks.updateTaskStatus', task);
}
});
return result;
}
}
}
}
Here is the offline initialisation based on Spencer’s tutorial:
const onRehydration = (store) => {
const data = Meteor.getData();
const db = data && data.db;
if (db) {
_.each(store.getState(), (collectionData, collectionName) => {
if (collectionName !== 'offlineUser' && collectionName !== 'offlineOrg' && collectionName !== 'actionQueue' && collectionName !== 'clipboard') {
if (!db[collectionName]) {
db.addCollection(collectionName);
}
const collectionArr = _.map(collectionData, (doc, _id) => {
doc._id = _id;
return doc;
});
db[collectionName].upsert(collectionArr);
}
});
}
store.dispatch({type: 'CACHING', caching: false})
};
export const initializeOffline = (opts = {}) => {
let debug = false;
const logger = createLogger({ predicate: () => debug&&opts.log || false });
const store = createStore(reducers, applyMiddleware(logger), autoRehydrate());
persistStore(store, {
storage: AsyncStorage,
keyPrefix: 'offline:',
debounce: opts.debounce || 2000,
}, () => onRehydration(store));
store.dispatch({type: 'CACHING', caching: true})
Meteor.ddp.on('added', (payload) => {
store.dispatch({ type: 'DDP_ADDED', payload });
});
Meteor.ddp.on('changed', (payload) => {
store.dispatch({ type: 'DDP_CHANGED', payload });
});
Meteor.ddp.on('removed', (payload) => {
store.dispatch({ type: 'DDP_REMOVED', payload });
});
return store;
};
If someone has an idea of the problem or ever encountered a similar issue, I’d be grateful if you could share your solution :)