I have a question regarding Next.js with GraphQL/Apollo and Typescript. A Next.js page I am trying to understand exports a getServerSideProps function. How can I access the props or rather the data that I fetch in getServerSideProps in the page?
I can see it when I log the props (see code), but it is in the object in a rather unusual format, with the id in the key. My IDE doesn't allow me to get to them via autocomplete; it only "sees" the normal prop graphQlClientId.
Bonus question: I am also unsure what the ctx which is passed into the getServerSideProps is and where it is coming from.
export interface PageProps {
graphQlClientId: string;
}
export default function Page(props: PageProps) {
const { graphQlClientId } = props;
console.log(props);
// {
// ...
// graphQlClientId: "12dpZ450LTsadsf",
// __APOLLO_STATE__: {
// ROOT_QUERY: {
// user({"id":"12dpZ450LTsadsf"}): {
// name: 'Carl C'
// }
// }
// }
// }
return (
<Layout graphQlClientId={graphQlClientId}>
<SomeComponent user={user} />
</Layout>
);
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const { apolloClient, clientId, authToken, csrfToken } = await getApolloClient(ctx);
const userDataRequest = apolloClient.query({
query: GetUserDocument,
variables: { clientId: clientId },
});
await userDataRequest.catch((err) => {
console.error(err);
});
return withApolloState({ apolloClient, authToken, clientId, csrfToken });
};
First the easy question, ctx is the context parameter for the getServerSideProps function. It contains the request object, the response object, route parameters, query parameters, and so on...
You use the context to create the initial data for the page i.e. you get a query parameter 'id' and query the database for that ID. Then you have that data available in pageProps.
See this page https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props for more detailed info.
The main question. When you return return withApolloState({ apolloClient, authToken, clientId, csrfToken }); you are sending this object { apolloClient, authToken, clientId, csrfToken } to the pageProps.
Here you are not setting a type, so you can't "see" what is there with your IDE. Also, is that the data that you want to use in your page?
To fix it, define a type and return an object of that type:
type T {
clientId string
authToken string
...
}
export const getServerSideProps: GetServerSideProps = async (ctx) : T => {
...
return { ... } as T;
}
Related
Well, I have this very simple file where I export a QueryClient from react-query library:
/queryClient.ts
import { QueryClient } from "react-query";
export const queryClient = new QueryClient();
Is there a way to set up a base url, or should I use something like context provider in order to do it?
Well, as shown in this article:
React Query and TypeScript
And on the docs:
Default Query Function
You can configure a default query function:
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryFn: async ({ queryKey: [url] }) => {
if (typeof url === 'string') {
const { data } = await axios.get(`${BASE_URL}/${url.toLowerCase()}`)
return data
}
throw new Error('Invalid QueryKey')
},
},
},
});
Being able to call the UseQuery instance passing only the params of your api method call:
// You can even leave out the queryFn and just go straight into options
function Post({ postId }) {
const { status, data, error, isFetching } = useQuery(`/posts/${postId}`, {
enabled: !!postId,
})
// ...
}
In one of my projects, I need to fill the meta keyword and the meta description by the data which is fetched from a REST API request.
so I used the getServerSideProps function, to fetch the response and pass it to the page.
Here's my getServerSideProps function
export async function getServerSideProps(context) {
function makeParam() {
let params = new URLSearchParams(context.params);
let keysForDel = [];
params.forEach((v, k) => {
if (v === 'undefined')
keysForDel.push(k)
});
keysForDel.forEach(k => {
params.delete(k)
});
return params.toString()
}
let response = await axios.post(
process.env.baseAddress + "getData.php",
qs.stringify({
data: "api/search/advance?" + makeParam()
}),
{
headers: {
'content-type': 'application/x-www-form-urlencoded'
}
})
return {
props: {
dataFromServer: response.data,
params: makeParam()
},
}
}
everything works fine in development mode (localhost), but after deploying, by refreshing the page Context parameter is an empty object.
this function was written in one of the pages that has one parameter called the city, which is shown below
I have already checked getServerSideProps props empty object.
as Ankri said
Can you please check, that you pass the pageProps to your custom Component?
here is my Component tag, which contains pageProps.
<Layout>
<Component {...pageProps} ref={current}/>
</Layout>
First make the file structure like this:
pages:
city:
[...slug].js
Note: city is folder!
Now you can get the first param like this:
export async function getServerSideProps(context) {
const slug = context?.query?.slug[0] || "";
const req = await axios.post(`${url}?advance=${slug}`)
// ... rest of the code
}
now if url looks like this => http://localhost:300/city/japan
slug = japan
This is my apollo client code.
import {
ApolloClient,
ApolloLink,
createHttpLink,
InMemoryCache
} from "#apollo/client"
import { setContext } from "#apollo/client/link/context"
let uri = `${process.env.NEXT_PUBLIC_API_URL}/wp/graphql`
const httpLink = createHttpLink({ uri, credentials: "include" })
const authLink = setContext((_, { headers }) => {
headers
})
let client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: "no-cache"
}
}
})
export { client }
This is my page routing in next where i am trying a simple GraphQL Query.
export default function PrivatePath(props: any) {
console.log("props:", props)
const { data, loading, error } = useQuery(gql`
query MyQuery {
page(id: "/min-sida", idType: URI) {
status
title
}
}
`)
console.log("data:", data)
return (
<ApolloProvider client={client}>
<AuthProvider>
<div></div>
</AuthProvider>
</ApolloProvider>
)
}
export async function getServerSideProps(context: any) {
const slugs = context.query.path[0]
const query = gql`
query MyQuery {
page(id: "/min-sida", idType: URI) {
status
title
}
}
`
const data = await client.query({ query: query })
return {
props: data
}
}
What is interesting to me is that the hook useQuery, does what is expected and when logged in delivers the page title and status.
The client.query, however, does not ever return page title and status, even when logged in it simple returns page: { null }.
My initial thought was that it was because getStatiProps in next won't be able to get the data no matter what, but it seems getServerSideProps is unable to do so as well?
Does anyone have a decent idea for solving this?
So I'm creating authentication logic in my Next.js app. I created /api/auth/login page where I handle request and if user's data is good, I'm creating a httpOnly cookie with JWT token and returning some data to frontend. That part works fine but I need some way to protect some pages so only the logged users can access them and I have problem with creating a HOC for that.
The best way I saw is to use getInitialProps but on Next.js site it says that I shouldn't use it anymore, so I thought about using getServerSideProps but that doesn't work either or I'm probably doing something wrong.
This is my HOC code:
(cookie are stored under userToken name)
import React from 'react';
const jwt = require('jsonwebtoken');
const RequireAuthentication = (WrappedComponent) => {
return WrappedComponent;
};
export async function getServerSideProps({req,res}) {
const token = req.cookies.userToken || null;
// no token so i take user to login page
if (!token) {
res.statusCode = 302;
res.setHeader('Location', '/admin/login')
return {props: {}}
} else {
// we have token so i return nothing without changing location
return;
}
}
export default RequireAuthentication;
If you have any other ideas how to handle auth in Next.js with cookies I would be grateful for help because I'm new to the server side rendering react/auth.
You should separate and extract your authentication logic from getServerSideProps into a re-usable higher-order function.
For instance, you could have the following function that would accept another function (your getServerSideProps), and would redirect to your login page if the userToken isn't set.
export function requireAuthentication(gssp) {
return async (context) => {
const { req, res } = context;
const token = req.cookies.userToken;
if (!token) {
// Redirect to login page
return {
redirect: {
destination: '/admin/login',
statusCode: 302
}
};
}
return await gssp(context); // Continue on to call `getServerSideProps` logic
}
}
You would then use it in your page by wrapping the getServerSideProps function.
// pages/index.js (or some other page)
export const getServerSideProps = requireAuthentication(context => {
// Your normal `getServerSideProps` code here
})
Based on Julio's answer, I made it work for iron-session:
import { GetServerSidePropsContext } from 'next'
import { withSessionSsr } from '#/utils/index'
export const withAuth = (gssp: any) => {
return async (context: GetServerSidePropsContext) => {
const { req } = context
const user = req.session.user
if (!user) {
return {
redirect: {
destination: '/',
statusCode: 302,
},
}
}
return await gssp(context)
}
}
export const withAuthSsr = (handler: any) => withSessionSsr(withAuth(handler))
And then I use it like:
export const getServerSideProps = withAuthSsr((context: GetServerSidePropsContext) => {
return {
props: {},
}
})
My withSessionSsr function looks like:
import { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
import { IronSessionOptions } from 'iron-session'
const IRON_OPTIONS: IronSessionOptions = {
cookieName: process.env.IRON_COOKIE_NAME,
password: process.env.IRON_PASSWORD,
ttl: 60 * 2,
}
function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, IRON_OPTIONS)
}
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
function withSessionSsr<P extends { [key: string]: unknown } = { [key: string]: unknown }>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) {
return withIronSessionSsr(handler, IRON_OPTIONS)
}
export { withSessionRoute, withSessionSsr }
I have set up exportPathMap but I get an empty object when exporting getStaticProps from my component. I don't understand how I'm supposed to access it?
// next.config.js
exportPathMap: async function (
defaultMapPath,
{ dev, dir, outDir, distDir, buildId }
) {
const paths = {}
const response = await fetch(...)
const data = await response.json()
data.forEach((item) => {
paths[`/item/${item.id}`] = {
page: '/itemPage',
query: {
item,
},
}
})
return paths
},
And
// itemPage.js
...
export async function getStaticProps(props) {
console.log('props', props) // returns "props {}""
return {
props: props
}
}
...
It's difficult to find it specifically stated in the documentation right now, but here are your options.
OPTION 1
Use getStaticPaths and getStaticProps like so (from "with-static-export" in the nextJS examples)
// [id].js post file
export async function getStaticPaths() {
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts?_page=1'
)
const postList = await response.json()
return {
paths: postList.map(post => {
return {
params: {
id: `${post.id}`,
},
}
}),
fallback: false,
}
}
export async function getStaticProps({ params }) {
// fetch single post detail
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.id}`
)
const post = await response.json()
return {
props: post,
}
}
OPTION 2
Use exportPathMap AND getInitialProps
// item.js
...
Page.getInitialProps = function (context) {
return context.query.item
}
// next.config.js
module.exports = {
// ...Same as in your question...
}
Both will allow you to have static optimization. With Option 1, you need not export, next build is sufficient. For Option 2 you will need to run next export
From: https://nextjs.org/docs/advanced-features/static-html-export
If your pages don't have getInitialProps you may not need next export at all; next build is already enough thanks to Automatic Static Optimization.
Another note: You can use both methods inside a single project.