OS: Windows 10 Pro.
Next: 8.1.0.
Express server: 4.16.4.
So, I'm attempting to access the ctx.req property, from a custom express server, so as to gain access to req.ip, which I utilise around the site using react context, but am getting an undefined value response. How do I resolve this?
My code is as follows:
Usercontext.js
import { createContext } from 'react';
const UserContext = createContext();
export default UserContext;
_app.js
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
// this exposes the query to the user
pageProps.query = ctx.query;
pageProps.req = ctx.req; // This line causes the site to break
return { pageProps };
}
render() {
const { Component, apollo, pageProps } = this.props;
return (
<Container>
<ApolloProvider client={apollo}>
<UserContext.Provider value={{ userip: pageProps.req }}>
<Page>
<Component {...pageProps} />
</Page>
</UserContext.Provider>
</ApolloProvider>
</Container>
);
}
}
And I access the value in each required component as follows:
const somePageComponent = props => {
const { userip } = useContext(UserContext);
}
I initially attempted to do pageProps.req = ctx.req, in _app.js, but that causes an Error: Circular structure in "getInitialProps" result of page "/_error" to occur
Server.js
const app = next({ dev })
const handle = app.getRequestHandler()
const ssrCache = cacheableResponse({
ttl: 1000 * 60 * 60, // 1hour
get: async ({ req, res, pagePath, queryParams }) => ({
data: await app.renderToHTML(req, res, pagePath, queryParams)
}),
send: ({ data, res }) => res.send(data)
})
server.set('trust proxy', true);
// Header security. See: https://observatory.mozilla.org/
server.use(helmet());
// Sets "Referrer-Policy: same-origin".
server.use(helmet.referrerPolicy({ policy: 'same-origin' }));
// Sets Feature-policy
server.use(helmet.featurePolicy({
features: {
fullscreen: ["'self'"],
vibrate: ["'none'"],
payment: ['https://yyy.com'],
syncXhr: ["'self'"],
geolocation: ["'self'"]
}
}));
app.prepare().then(() => {
//const server = express()
server.get('*', function(req,res,next) {
if(req.headers['x-forwarded-proto'] != 'https' && process.env.NODE_ENV === 'production')
res.redirect('https://'+req.hostname+req.url)
else
next() /* Continue to other routes if we're not redirecting */
});
server.get('/', (req, res) => ssrCache({ req, res, pagePath: '/' }))
server.get('*', (req, res) => handle(req, res))
server.listen(port, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
You should be able to just take what you need from req rather than taking the whole object, which as you know, gives you an error.
For example, if you are trying to get the user's IP address from a custom header then something like this should work:
pageProps.userip = ctx.req.headers['x-userip']
...and then:
<UserContext.Provider value={{ userip: pageProps.userip }}>
I hope this helps.
MyApp.getInitialProps = async (appContext) => {
const ip = appContext.ctx.req.connection.remoteAddress;
...
return {
props: {}
}
};
export async function getServerSideProps(context) {
const ip = context.req.headers['x-forwarded-for']
return {
props: {
ip
}
}
}
Related
I'm building NextJS app with SSR. I've written the getServerSideProps function that makes a call to supabase. Before making the call I'm trying to get user session by calling getSession function from #auth0/nextjs-auth0 package.
I'm trying to mock it in the handlers.ts file:
import { rest } from 'msw';
export const handlers = [
// this is the endpoint called by getSession
rest.get('/api/auth/session', (_req, res, ctx) => {
return res(ctx.json(USER_DATA));
}),
rest.get('https://<supabase-id>.supabase.co/rest/v1/something', (_req, res, ctx) => {
return res(ctx.json(SOMETHING));
}),
];
My mocks file: requestMocks/index.ts:
export const initMockServer = async () => {
const { server } = await import('./server');
server.listen();
return server;
};
export const initMockBrowser = async () => {
const { worker } = await import('./browser');
worker.start();
return worker;
};
export const initMocks = async () => {
if (typeof window === 'undefined') {
console.log('<<<< setup server');
return initMockServer();
}
console.log('<<<< setup browser');
return initMockBrowser();
};
initMocks();
Finally, I'm calling it in the _app.tsx file:
if (process.env.NEXT_PUBLIC_API_MOCKING === 'true') {
require('../requestMocks');
}
Unfortunately, it does work for me. I'm getting no user session data in the getServerSideProps function in my page component:
import { getSession } from '#auth0/nextjs-auth0';
export const getServerSideProps = async ({ req, res }: { req: NextApiRequest; res: NextApiResponse }) => {
const session = getSession(req, res);
if (!session?.user.accessToken) {
// I'm constantly falling here
console.log('no.session');
return { props: { something: [] } };
}
// DO something else
};
Any suggestions on how to make it working in Cypress tests would be great.
I'm expecting that I will be able to mock requests made in getServerSideProps function with MSW.js library.
I made it finally. Looks like I don't have to mock any calls. I need to copy my user appSession cookie and save it in cypress/fixtures/appSessionCookie.json file:
{
"appSession": "<cookie-value>"
}
Then use it in tests as follows:
before(() => {
cy.fixture('appSessionCookie').then((cookie) => {
cy.setCookie('appSession', cookie.appSession);
});
});
This makes a user automatically logged in with Auth0.
I want to build my next js project in which i am using
https://www.npmjs.com/package/#react-oauth/google
but when I build it i get the following :
this is layout.js and in _app.js I have all the components wrapped in GoogleOAuthProvider
import { GoogleLogin } from '#react-oauth/google';
import {FcGoogle} from "react-icons/Fc"
import { useGoogleLogin } from '#react-oauth/google';
export default function Layout({ children }) {
const client_id = ""
const responseGoogle = (response) => {
console.log(response);
}
CUTTED (NOT RELEVANT)
const login = useGoogleLogin({
onSuccess: codeResponse => {
const { code } = codeResponse;
console.log(codeResponse)
axios.post("http://localhost:8080/api/create-tokens", { code }).then(response => {
const { res, tokens } = response.data;
const refresh_token = tokens["refresh_token"];
const db = getFirestore(app)
updateDoc(doc(db, 'links', handle), {
refresh_token : refresh_token
})
updateDoc(doc(db, 'users', useruid), {
refresh_token : refresh_token
}).then(
CUTTED (NOT RELEVANT)
)
}).catch(err => {
console.log(err.message);
})
},
onError: errorResponse => console.log(errorResponse),
flow: "auth-code",
scope: "https://www.googleapis.com/auth/calendar"
});
return (
<>
CUTTED (NOT RELEVANT)
</>
)
}
Everything works perfect in dev mode but it does not want to build
I've faced this issue too. So I use 'GoogleLogin' instead of 'useGoogleLogin', then you can custom POST method on 'onSuccess' property.
import { GoogleLogin, GoogleOAuthenProvider} from '#react-oauth/google';
return(
<GoogleOAuthProvider clientId="YOUR CLIENT ID">
<GoogleLogin
onSuccess={handleLogin}
/>
</GoogleOAuthProvider>
The async function will be like...
const handleLogin = async = (credentialResponse) => {
var obj = jwt_decode(credentialResponse.credential);
var data = JSON.stringify(obj);
console.log(data);
const data = {your data to send to server};
const config = {
method: 'POST',
url: 'your backend server or endpoint',
headers: {},
data: data
}
await axios(config)
}
Spending whole day, this solve me out. Just want to share.
You have to wrap your application within GoogleOAuthProvider component. Please keep in mind that you will need your client ID for this.
import { GoogleOAuthProvider } from '#react-oauth/google';
<GoogleOAuthProvider clientId="<your_client_id>">
<SomeComponent />
...
<GoogleLoginButton onClick={handleGoogleLogin}/>
</GoogleOAuthProvider>;
I am serving my next.js on Vercel (I tried Amplify as well). Page works but components that require data from getServerSideProps() in my pages/index.tsx are not required. It seems that function isn't called at all.
Could anyone help me fix this issue?
export default function Home({ cryptosData, tempObject }) {
return (
<>
{tempObject && <Converter tempObject={tempObject} />}
{cryptosData && <MainTable data={cryptosData} />}
</>
);
}
export const getServerSideProps = async () => {
const url =
"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest";
const query = "?limit=80";
try {
const res = await fetch(url + query, {
headers: { "X-CMC_PRO_API_KEY": process.env.API_KEY },
});
const json = await res.json();
const data = json.data;
const [cryptosData, tempObject] = parseData(data, 40);
return {
props: { cryptosData, tempObject },
};
} catch (error) {
console.log(error);
return {
props: {},
};
}
};
I had the same issue. And the reason was in headers I sent with API requests
I'm building a application using nextJS.
In server/index.ts, I have :
expressApp.get('/', (req: express.Request, res: express.Response) => {
const parsedUrl = parse(req.url, true);
const { query } = parsedUrl;
let username: string | undefined;
if (process.env.STAGE !== 'local') {
username = getUsername(req)?.toString();
}
return nextApp.render(req, res, '/', {...query, loggedInUser: username});
});
and in my home page (path is '/'), I auto direct to path '/pageone' by doing:
const Home = () => {
const router = useRouter();
useEffect(() => {
router.push('/pageone', undefined, { shallow: true });
}, []);
return <PageOne />;
};
Home.getInitialProps = async (ctx: NextPageContext): Promise<{ username : string | string[] }> => {
const query = ctx.query;
return {
username: query.loggedInUser? query.loggedInUser : 'testUser'
};
};
export default Home;
I need this username variable in every page I build, how can I pass it to every page(for example pageone)?
You can attach this username variable to the res.locals which this is it's purpose.
Then access the res.locals of the getInitialProps ctx.
// server.ts
// this express middleware will attach username to res.locals.username;
expressApp.use((req, res, next) => {
res.locals = res.locals || {};
if (process.env.STAGE !== 'local') {
res.locals.username = getUsername(req)?.toString();
}
next();
});
expressApp.get('/', (req: express.Request, res: express.Response) => {
const parsedUrl = parse(req.url, true);
const {query} = parsedUrl;
return nextApp.render(req, res, '/', query);
});
Then instead of passing this value to the client, as you did, you can redirect from server side within getInitialProps.
// Home.tsx
const Home = () => {
return <PageOne />;
};
Home.getInitialProps = async ({res}: NextPageContext): Promise<{ username : string | string[] }> => {
if(res.locals.username === 'bla') {
return res.redirect(301, '/pageone');
// -----------^ will redirect at server side.
}
};
export default Home;
I've made a module that might help. 👍
Next Coke API allows typed communication between client and server:
Server:
// define API methods
const routes = {
getName: async (body) => {
return "your name is " + body.name
}
}
// export types to the client
export type AppRoutes = typeof routes
// export nextCokeHandler
export default function handler(req, res) {
return nextCokeHandler(req, res, routes)
}
Client:
// define coke client
const { coke } = nextCokeClient<AppRoutes>()
// call API methods
coke.getName({ name: "John" }).then((res) => {
console.log(res)
})
[id].tsx
const Home:NextPage<any> = (props) => {
return (
<div>
{JSON.stringify(props)}
</div>
)
}
Home.getInitialProps = async (props) => {
// getting data from Database if we have an item which matched props.query.id;
const response = await axios.get('https://MY_API.com/'+props.query.id);''
// response format is like this
/*
response: {
status: 200 | 500,
item: Item | undefined
}
*/
//If response has no item, I would like to show _error.tsx instead [id].tsx
return { ...props, response };
}
export default Home;
_error.tsx
const Error:NextPage<any> = (props) => {
return <div>ERROR PAGE</div>
}
export default Error;
I've found one solution, it is redirecting to /_error but I don't want to change the URL.
localhost:3000/EXIST_ID => show [id].tsx and keep URL
localhost:3000/NOT_EXIST_ID => show _error.tsx and keep URL
You will need to use custom server, and render the "error" page when the id is not exists.
const express = require('express')
const next = require('next')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = express()
server.get('/:id', (req, res) => {
const page = IS_ID_EXISTS? '/posts' : '/_error';
return app.render(req, res, page, { id: req.params.id })
})
server.all('*', (req, res) => {
return handle(req, res)
})
server.listen(port, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})