Apollo client can't query in Nextjs getServerSideProps via SchemaLink - next.js

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?

Related

NextJs: getServerSideProps is not working with Apollo: fetch failed

I'm trying to generate the page from the server side page on Next.js, But I'm having a problem with it, so I have created an Apollo instance and I'm importing a Query from my queries, and I pass the variable as I do on useQuery from apollo on the client, because I don't know another way to do that, also how to handle errors on this?
Here are my getServerSideProps:
export async function getServerSideProps(context) {
const slug = context.params.slug;
const data = await Static.query({
query: LANDING,
variables: { slug },
});
return {
props: {
data: data,
},
};
}
Here is my query:
import gql from "graphql-tag";
export const CATEGORIES = gql`
query CategoriesView {
CategoriesView {
_id
Name
Description
Icon
}
}
`;
Here is my Client:
import {
ApolloClient,
HttpLink,
ApolloLink,
InMemoryCache,
} from "#apollo/client";
const uri = "http://localhost:3000/api"
const httpLink = new HttpLink({uri});
export const Apollo = new ApolloClient({
ssr: typeof window === "undefined" ? true : false,
cache: new InMemoryCache(),
link: ApolloLink.from([httpLink]),
});
But I get this error: failed to fetch
Here is a screenshot of it:
Here is my example, working in my case.
import { useSubscription, useQuery, gql } from "#apollo/client";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
//your query here ; first check if your query works (use your playground)
const QUERY = gql`
query {
customers{
data{
attributes{
firstname
lastname
location
phone
createdAt
}
}
}
}
`;
const Page = () => {
//apollo function
const { data, loading, error } = useQuery(QUERY);
if (loading) return <div>Loading...</div>
if (error) return <div>Failed to load!</div>
return (
<>
{JSON.stringify(data)}
</>
)
};
//can do with staticProps, serverProps, etc. Below just an example - delete if not needed
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ["common"])),
},
};
}
export default Page;
I am not sure if you can put gql query in getServerSideProps because apollo creates some sort of "cache" for your queries.
And check #apollo/client library

Axios getStore is undefined in NextJs api calls. (Redux, NextJs, Jwt)

I am trying to set up authentication for a project. Once a user signs up for our app they get sent to our home page with an id in the query. This id then gets used to submit user and then the jwt token gets saved inside redux state.
All our calls now go through an axios client where the jwt token is passed on every request. The token gets read with store.getState(injectStore)
This all works fine inside getserversideProps, but the issue comes in when using calls on the frontend that goes through NextJs built in 'pages/api' folder. Any calls inside those folders causes the store.getState() to be undefined. I do not understand why since it uses the exact same client as geserversideProps.
Example GetServersideProps(working)
try {
const response = await serverApiClient.get('v1/config');
return {
props: {
},
};
} catch ({ error: { statusCode = 500, message = 'Internal Server Error' } }) {
if (statusCode === 401) {
return {
redirect: {
permanent: false,
destination: '/',
},
};
}
throw new Error(message as string);
}
};
Example Frontend bff call(not working)
try {
// Call below get sent to next built in api
const players = await apiClient.get(`/defenders?sortBy=${statId}&team_id=${teamShortName}`);
return players;
} catch (error) {
return { error };
}
};
export default async function handler(req: NextApiRequest) {
console.log('Start request')
try {
const { sortBy, team_id: teamId } = req.query;
const response = await serverApiClient.get(`/v1/players/picks?position=DEF&sort_by=${sortBy}&team_id=${teamId}`);
Api Client
mergeConfigs(
params: Record<string, string>,
headers: Record<string, string>,
configs: Record<string, string>,
): AxiosRequestConfig {
const defaultConfigs = ApiClient.getDefaultConfigs();
*const token = store?.getState()?.jwtToken?.value*
//ISSUE ABOVE - This store .getState() is only undefined in nextJS api folder calls.
return {
...defaultConfigs,
...configs,
params,
headers: {
...defaultConfigs.headers,
...headers,
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
};
}
get(
uri: string,
params = {},
headers = {},
configs = {},
): Promise<AxiosResponse | any> {
return this.client
.get(uri, this.mergeConfigs(params, headers, configs))
.then((response) => {
return (response.data ? response.data : response);
})
.catch((error) => {
const errorObject = {
error: error?.response?.data,
};
throw Object.assign(errorObject);
});
}
If anyone has some advice on why that getStore is undefined in frontend-to-backend calls please assist. Thanks all!

Get supabase `user` server side in next.js

I am attempting to get the current logged in supabase user while server side.
I have attempted to use const user = supabase.auth.user(); but I always get a null response.
I have also attempted const user = supabase.auth.getUserByCookie(req) but it also returns null. I think because I am not sending a cookie to the api when calling it from the hook.
I have tried passing the user.id from the hook to the api but the api is not receiving the parameters.
I also attempted this approach but the token is never fetched. It seems to not exist in req.cookies.
let supabase = createClient(supabaseUrl, supabaseKey);
let token = req.cookies['sb:token'];
if (!token) {
return
}
let authRequestResult = await fetch(`${supabaseUrl}/auth/v1/user`, {
headers: {
'Authorization': `Bearer ${token}`,
'APIKey': supabaseKey
}
});
`
Does anyone know how to get the current logged in user in server side code?
If you need to get the user in server-side, you need to set the Auth Cookie in the server using the given Next.js API.
// pages/api/auth.js
import { supabase } from "../path/to/supabaseClient/definition";
export default function handler(req, res) {
if (req.method === "POST") {
supabase.auth.api.setAuthCookie(req, res);
} else {
res.setHeader("Allow", ["POST"]);
res.status(405).json({
message: `Method ${req.method} not allowed`,
});
}
}
This endpoint needs to be called every time the state of the user is changed, i.e. the events SIGNED_IN and SIGNED_OUT
You can set up a useEffect in _app.js or probably in a User Context file.
// _app.js
import "../styles/globals.css";
import { supabase } from '../path/to/supabaseClient/def'
function MyApp({ Component, pageProps }) {
useEffect(() => {
const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
handleAuthChange(event, session)
if (event === 'SIGNED_IN') {
// TODO: Actions to Perform on Sign In
}
if (event === 'SIGNED_OUT') {
// TODO: Actions to Perform on Logout
}
})
checkUser()
return () => {
authListener.unsubscribe()
}
}, [])
return <Component {...pageProps} />;
}
async function handleAuthChange(event, session) {
await fetch('/api/auth', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'same-origin',
body: JSON.stringify({ event, session }),
})
}
export default MyApp;
You can now handle this user with a state and pass it to the app or whichever way you'd like to.
You can get the user in the server-side in any Next.js Page
// pages/user_route.js
import { supabase } from '../path/to/supabaseClient/def'
export default function UserPage ({ user }) {
return (
<h1>Email: {user.email}</h1>
)
}
export async function getServerSideProps({ req }) {
const { user } = await supabase.auth.api.getUserByCookie(req)
if (!user) {
return { props: {}, redirect: { destination: '/sign-in' } }
}
return { props: { user } }
}
Here's a YouTube Tutorial from Nader Dabit - https://www.youtube.com/watch?v=oXWImFqsQF4
And his GitHub Repository - https://github.com/dabit3/supabase-nextjs-auth
supabase have a library of helpers for managing auth for both client- and server-side auth and fetching in a couple of frameworks including Next.js: https://github.com/supabase/auth-helpers and appears to be the recommended solution for similar problems based on this thread: https://github.com/supabase/supabase/issues/3783
This is how I'm using it in an API handler, but provided you have access to req, you can access the user object this way:
import { supabaseServerClient } from '#supabase/auth-helpers-nextjs';
const { user } = await supabaseServerClient({ req, res }).auth.api.getUser(req.cookies["sb-access-token"]);
Note that you will need to use the helper library supabaseClient and supabaseServerClient on the client and server side respectively for this to work as intended.
I was following a tutorial today and was having a similar issue and the below is how i managed to fix it.
I've got this package installed github.com/jshttp/cookie which is why i'm calling cookie.parse.
Supabase Instance:
`//../../../utils/supabase`
import { createClient } from "#supabase/supabase-js";
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_KEY
);
In my case this was my API page:
import { supabase } from "../../../utils/supabase";
import cookie from "cookie";
import initStripe from "stripe";
const handler = async (req, res) => {
const { user } = await supabase.auth.api.getUserByCookie(req);
if (!user) {
return res.status(401).send("Unathorized");
}
const token = cookie.parse(req.headers.cookie)["sb-access-token"];
supabase.auth.session = () => ({
access_token: token,
});`
const {
data: { stripe_customer },
} = await supabase
.from("profile")
.select("stripe_customer")
.eq("id", user.id)
.single();
For anyone who tries to figure out how to get the user server side with the new #supabase/auth-helpers-nextjs, Michele gave the answer.
Just a note: If you're trying to get the user on nextJs's Middleware, instead of:
... req.cookies["sb-access-token"]
You have to use: req.cookies.get('sb-access-token')
For example:
import { supabaseServerClient } from '#supabase/auth-helpers-nextjs';
const { user } = await supabaseServerClient({ req, res }).auth.api.getUser(req.cookies.get('sb-access-token'))
UPDATE: 2023. Available now on Supabase Docs here
import { createServerSupabaseClient } from '#supabase/auth-helpers-nextjs'
export default function Profile({ user }) {
return <div>Hello {user.name}</div>
}
export const getServerSideProps = async (ctx) => {
// Create authenticated Supabase Client
const supabase = createServerSupabaseClient(ctx)
// Check if we have a session
const {
data: { session },
} = await supabase.auth.getSession()
if (!session)
return {
redirect: {
destination: '/',
permanent: false,
},
}
return {
props: {
initialSession: session,
user: session.user,
},
}
}

Get token from cookie using Apollo Client and Next-Auth

can you help to check my code is there anyway that i can get access token from cookie and send it through apollo client? In my Next.js project i am using NextAuth for authentication. After user logged in I save user information and access token inside session. But i have know idea how can i get it and pass it with apollo client.
import { useMemo } from 'react'
import { ApolloClient, ApolloLink, InMemoryCache, createHttpLink } from '#apollo/client'
import { setContext } from '#apollo/client/link/context';
import { concatPagination } from '#apollo/client/utilities'
import merge from 'deepmerge'
import isEqual from 'lodash/isEqual'
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'
let apolloClient
const CLIENT_URL =
process.env.NODE_ENV === 'production'
? process.env.API_END_POINT
: 'http://localhost:1337'
function createApolloClient() {
const httpLink = createHttpLink({
uri: `${CLIENT_URL}/graphql`,
credentials: 'same-origin'
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
}
});
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
jobs: concatPagination(),
},
},
},
});
const client = new ApolloClient({
srMode: typeof window === 'undefined',
link: authLink.concat(httpLink),
cache
});
return client;
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract()
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState, existingCache, {
// combine arrays using object equality (like in sets)
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter((d) =>
sourceArray.every((s) => !isEqual(d, s))
),
],
})
// Restore the cache with the merged data
_apolloClient.cache.restore(data)
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function addApolloState(client, pageProps) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract()
}
return pageProps
}
export function useApollo(pageProps) {
const state = pageProps[APOLLO_STATE_PROP_NAME]
const store = useMemo(() => initializeApollo(state), [state])
return store
}

App working locally but not on production: TypeError: Cannot read property 'titulo_categoria' of undefined

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!

Resources