Using createPages with User Auth and static content - node.js

I want to generate pages from my GraphQl calls and I also want to enable the create page when the window path finds the app term, as you would when switching to a more app approach. I heard I could make it work by putting the if statement inside of the graphql promise I'm not really sure how. My build doesn't complete it gives me path is not defined. Any help is appreciated
const path = require(`path`)
// Log out information after a build is done
exports.onPostBuild = ({reporter}) => {
reporter.info(`Your Gatsby site has been built!`)
}
exports.createPages = ({page, actions, graphql}) => {
const {createPage} = actions
if (page.path.match(/^\/app/)) {
// page.matchPath is a special key that's used for matching pages
// with corresponding routes only on the client.
page.matchPath = '/app/*'
// Update the page.
createPage(page)
}
let query1 = new Promise((resolve, reject) => {
graphql(`
{
allDatoCmsBlog {
edges {
node {
slug
}
}
}
}
`).then(result => {
result.data.allDatoCmsBlog.edges.map(({node}) => {
createPage({
path: `blog/${node.slug}`,
component: path.resolve(`./src/templates/article.js`),
context: {
slug: node.slug,
},
})
})
resolve()
})
})
let query2 = new Promise((resolve, reject) => {
graphql(`
{
allDatoCmsBlog {
edges {
node {
slug
category
}
}
}
}
`).then(result => {
result.data.allDatoCmsBlog.edges.map(({node}) => {
createPage({
path: `blog/categoria/${node.category}`,
component: path.resolve(`./src/templates/gather-category.js`),
context: {
category: node.category,
},
})
})
})
resolve()
})
return query1, query2
}

Note that you are wrapping inside createPages instead of onCreatePage. You can keep both logics, but in two different export methods:
exports.createPages = ({page, actions, graphql}) => {
// your logic of page creation
}
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
if (page.path.match(/^\/app/)) {
page.matchPath = "/app/*"
createPage(page)
}
}
Alternatively, you can use the gatsby-plugin-create-client-paths plugin. In your gatsby-config.js:
{
resolve: `gatsby-plugin-create-client-paths`,
options: { prefixes: [`/app/*`] },
},
Simply as that.

Related

How can I build nodes and use them to create pages in gatsby?

I'm able to run my code in dev just fine and works as expected. Not sure what I'm doing wrong. Am I unable to create nodes and then build pages from them in gatsby-node.js? Is there a better way of doing this in gatsby that I'm not aware of?
The problem is when I try to build I receive error.
"gatsby-node.js" threw an error while running the sourceNodes lifecycle:
Request failed with status code 400
Here is my code:
exports.sourceNodes = async ({
actions,
createNodeId,
createContentDigest,
}) => {
const { createNode } = actions
const auth_token = Buffer.from(
`${client_id}:${client_secret}`,
'utf-8'
).toString('base64')
const token_url = 'https://accounts.spotify.com/api/token'
const data = qs.stringify({ grant_type: 'client_credentials' })
const response = await axios.post(token_url, data, {
headers: {
Authorization: `Basic ${auth_token}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
})
const access_token = await response.data.access_token
const spotifyData = await axios.get(
`https://api.spotify.com/v1/artists/${artistId}/albums`,
{
headers: {
Authorization: `Bearer ${access_token}`,
'Content-type': 'application/json',
},
params: {
limit: 50,
market: 'US',
groups: 'single,album',
},
}
)
const ALBUM_NODE_TYPE = `Album`
await spotifyData.data.items.forEach(album =>
createNode({
...album,
id: createNodeId(`${ALBUM_NODE_TYPE}-${album.id}`),
parent: null,
children: [],
internal: {
type: ALBUM_NODE_TYPE,
contentDigest: createContentDigest(album),
},
})
)
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const results = await graphql(`
{
collections: allFile {
distinct(field: sourceInstanceName)
edges {
node {
id
sourceInstanceName
childMarkdownRemark {
id
fields {
slug
}
frontmatter {
title
image
}
}
}
}
}
news: allFile(filter: { sourceInstanceName: { eq: "news" } }) {
nodes {
sourceInstanceName
childMarkdownRemark {
frontmatter {
title
image
blurb
url
}
id
fields {
slug
}
}
}
}
music: allAlbum(sort: { fields: release_date, order: DESC }) {
nodes {
id
href
images {
url
}
name
artists {
name
}
release_date
album_type
external_urls {
spotify
}
}
}
}
`)
// Music
await results.data.music.nodes.forEach(node => {
console.log(node)
if (node.errors) {
node.errors.forEach(e => console.error(e.toString()))
return Promise.reject(node.errors)
}
const id = node.id
const name = node.name.replace(/\s+/g, '_').toLowerCase()
createPage({
path: `music/${name}`,
component: path.resolve(`src/templates/music/music.js`),
// additional data can be passed via context
context: {
id,
field: { ...node },
},
})
})
await results.data.news.nodes.forEach(node => {
if (node.errors) {
node.errors.forEach(e => console.error(e.toString()))
return Promise.reject(node.errors)
}
const id = node.childMarkdownRemark.id
createPage({
path: `${node.sourceInstanceName}${node.childMarkdownRemark.fields.slug}`,
component: path.resolve(
`src/templates/${String(node.sourceInstanceName)}/${String(
node.sourceInstanceName
)}.js`
),
// additional data can be passed via context
context: {
id,
field: { ...node.childMarkdownRemark.frontmatter },
},
})
})
//News Collection
await results.data.collections.distinct.forEach(collection => {
if (collection.errors) {
collection.errors.forEach(e => console.error(e.toString()))
return Promise.reject(collection.errors)
} else {
if (collection !== 'news') {
return
} else {
const edges = results.data.collections.edges
const nodes = edges.filter(
e => e.node.sourceInstanceName === 'news'
)
createPage({
path: `/collections/news`,
component: path.resolve(
`src/templates/news/collections/news.js`
),
context: {
nodes: { ...nodes },
},
})
}
}
})
}
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
I also tried wrapping the createPage function in sourceNodes but that didn't work either.
I ended up creating a source-plugin and passing in my Spotify credentials as a string in gatbsy-config.js. It's not able to read env variable at build time but it is when running dev and I'm not sure I understand why that is.

Error: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render

import { setCookies, removeCookies } from "cookies-next";
import { useRouter } from "next/router";
import { useEffect } from "react";
const { URL } = process.env;
export const getServerSideProps = async (context) => {
const userAuthToken = context.req.cookies["authToken"];
const data = {
authToken: userAuthToken,
};
const requestJSON = JSON.stringify(data);
const response = await fetch(URL + "api/userFetch", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: requestJSON,
});
const responseData = await response.json();
return {
props: { datas: responseData },
};
};
const Home = ({ datas }) => {
const router = useRouter();
if (datas[0].error == true) {
useEffect(() => {
setTimeout(() => {
router.push("/");
}, 3000);
}, []);
removeCookies("authToken");
return <h1>Something Went Wrong</h1>;
} else {
return <h1>Welcome To Home{datas[0].error}</h1>;
}
};
export default Home;
This code is running fine on development server but when I try to build this code in production I get this error **
./pages/Home.js
28:5 Error: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?**
I tried everything I could but can't fix the error
Just do exactly what the error is telling you. Move the useEffect call out of the conditional block. You can still conditionally perform the operation within the hook. For example:
const Home = ({ datas }) => {
const router = useRouter();
useEffect(() => {
if (datas[0].error == true) {
setTimeout(() => {
router.push("/");
}, 3000);
}
}, []);
if (datas[0].error == true) {
removeCookies("authToken");
return <h1>Something Went Wrong</h1>;
} else {
return <h1>Welcome To Home{datas[0].error}</h1>;
}
};
Specifically, as the error states, the same hooks must always be called on every render. (I don't know enough under the hood of React to describe why that's the case, it just seems necessary for stability/consistency/etc.) But the operation being performed by the hook in this case can still be effectively a no-op if the intended condition is not met.

Node/Apollo/GraphQL - advice on using async/await in an Apollo Server plugin

Any advice on using async/await in an apollo plugin? I'm attempting to await a twilio service promise and running into the Can not use keyword 'await' outside an async function babel parser error and unsure how to convert the parent function to be async. Here's the basic layout:
export const twilioVerification = async () => {
return {
requestDidStart () {
return {
willSendResponse ({ operationName, response, context }) {
if (['UpdateUser', 'CreateUser', 'SignInByPhone'].includes(operationName)) {
const user = response.data[operationName];
if (user != null) {
await sendVerificationText(user.phoneNumber);
}
}
}
}
},
}
};
The above code throws the BABEL_PARSE_ERROR. I've attempted a variety of ways to add async to willSendResponse and/or requestDidStart with mixed unsuccessful results. For reference, here's how I am instantiating ApolloServer as well:
const server = new ApolloServer({
context: { driver, neo4jDatabase: process.env.NEO4J_DATABASE },
schema: schema,
introspection: process.env.APOLLO_SERVER_INTROSPECTION,
playground: process.env.APOLLO_SERVER_PLAYGROUND,
plugins: [
pushNotifications(firebase),
twilioVerification(),
]
})
The function that isn't async is your function. Just add async on your willSendResponse. Here is one way to do that:
export const twilioVerification = async () => {
return {
requestDidStart () {
return {
willSendResponse: async ({ operationName, response, context }) => {
if (['UpdateUser', 'CreateUser', 'SignInByPhone'].includes(operationName)) {
const user = response.data[operationName];
if (user != null) {
await sendVerificationText(user.phoneNumber);
}
}
}
}
},
}
};

How do I use multiple createPage routes in gatsby-node.js

Im currently having an issue when using multiple createPage routes in gatsby-node.js. I am trying to use Gatsby js with Shopify commerce storefront & and another CMS for blog articles so I need a way of creating routes when viewing products and when viewing blog articles respectively.
Currently, I am experiencing an error that only appears when trying to view a product detail page that reads:
(EnsureResources, ) TypeError: Cannot read property 'page' of undefined
My gatsby-node.js currently looks like this
const path = require(`path`)
// Create product page urls
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
return graphql(`
{
allShopifyProduct {
edges {
node {
handle
}
}
}
}
`).then(result => {
result.data.allShopifyProduct.edges.forEach(({ node }) => {
const id = node.handle
createPage({
path: `/product/${id}/`,
component: path.resolve(`./src/templates/product-page.js`),
context: {
id,
},
})
})
})
}
// Create blog post slug urls
exports.createPages = async ({graphql, actions}) => {
const {createPage} = actions
const blogTemplate = path.resolve('./src/templates/blog.js')
const res = await graphql (`
query {
allContentfulBlogPost {
edges {
node {
slug
}
}
}
}
`)
res.data.allContentfulBlogPost.edges.forEach((edge) => {
createPage ({
component: blogTemplate,
path: `/blog/${edge.node.slug}`,
context: {
slug: edge.node.slug
}
})
})
}
You can't define the same API (createPages) twice. Do it in one function, especially since you can all put it into one query.
This code is obviously untested, but should work:
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
{
shopify: allShopifyProduct {
nodes {
handle
}
}
contentful: allContentfulBlogPost {
nodes {
slug
}
}
}
`)
const shopifyTemplate = require.resolve(`./src/templates/product-page.js`)
const contentfulTemplate = require.resolve('./src/templates/blog.js')
if (result.errors) {
return
}
result.data.shopify.nodes.forEach(product => {
const id = product.handle
createPage({
path: `/product/${id}/`,
component: shopifyTemplate,
context: {
id,
},
})
})
result.data.contentful.nodes.forEach(post => {
createPage ({
component: contentfulTemplate,
path: `/blog/${post.slug}`,
context: {
slug: post.slug
}
})
})
}
The nodes is a shortcut for edges.node and valid syntax. The shopify: is an alias before the name of the query. You don't need to use path, you can also use require.resolve. The async/await syntax is better to read IMO.

Create posts at different folder

Hi I'm using Gatsby starter blog.
I have two pages - blog and projects. Right now I have six posts, five blog posts and one project post. All the posts including the project post are created under /blog page. How do I ensure projects posts are created in /projects page instead?
I have added gatsby-source-filesystem with the path ${__dirname}/content/projects into my gatsby-config.js already.
Gatsby-node.js
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const fileNode = getNode(node.parent);
const value = createFilePath({ node, getNode })
const parent = fileNode.sourceInstanceName;
let basePath = (parent === `blog`) ? `blog` : `projects`
createNodeField({
name: `slug`,
node,
value: `/${basePath}${value}`,
})
}
}
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
const blogPost = path.resolve(`./src/templates/blog-post.js`)
const projectPost = path.resolve(`./src/templates/project-post.js`)
return graphql(
`
{
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
limit: 1000
) {
edges {
node {
fields {
slug
}
frontmatter {
title
}
}
}
}
}
`
).then(result => {
console.log(JSON.stringify(result, null, 4));
if (result.errors) {
throw result.errors
}
// Create blog and projects posts.
const posts = result.data.allMarkdownRemark.edges
posts.forEach((post, index) => {
const slug = post.node.fields.slug;
createPage({
path: `/${slug}`,
component: blogPost,
context: {
slug,
},
})
})
return null
})
}

Resources