Here is my situation. I am trying to set up a Next.js project with an Express.js back-end. I did set up the back-end as a regular one not as a custom server according to Next.js documentation. So I am not sure if I am already making a mistake by setting up the back-end as a regular one. I am trying to fetch data from a back-end endpoint http://localhost:3500/api/v1/list using axios and it works well. But when I am trying to implement React-Query on the first load I am getting the right data from the back-end but when it is trying to re-fetch for some reason it is hitting the wrong end-point http://localhost:3600/api/v1/list and getting the 404 Not Found error. It looks like it is switching the port from 3500 to 3600 which is a front-end port. Here you will find the link to the repository and here is the code. Let me know if I am doing something wrong,
page/sell.js
import axios from 'axios';
import { useQuery } from 'react-query';
export default function SellPage({ response }) {
const { data, isLoading, error } = useQuery('sells', getPosts, {
initialData: response,
});
console.log('data useQuery: ', data);
if (isLoading) return 'Loading...';
if (error) return error.message;
return (
<div>
<p>Hello SellPage!!{data.message}</p>
</div>
);
}
export async function getServerSideProps(context) {
const response = await getPosts();
console.log('response', response);
if (!response) {
return {
notFound: true,
};
}
return {
props: { response }, // will be passed to the page component as props
};
}
async function getPosts() {
console.log('HOST: ', process.env.HOST);
const { data } = await axios.request({
baseURL: process.env.HOST,
url: '/api/v1/list',
method: 'get',
});
return data;
}
_app.js
import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
const queryClient = new QueryClient();
function MyApp({ Component, pageProps }) {
const queryClientRef = React.useRef();
if (!queryClientRef.current) {
queryClientRef.current = new QueryClient();
}
return (
<>
<QueryClientProvider client={queryClientRef.current}>
<Component {...pageProps} />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</>
);
}
export default MyApp;
I don't see a next.config.js in your repo, so I guess the env variables are not bundled in the js and in the end you url looks like localhost: or localhost:undefined which the browser default to the port your client is served.
Try add next.config.js
module.exports = {
env: {
HOST: process.env.HOST,
},
}
SEE: https://nextjs.org/docs/api-reference/next.config.js/environment-variables
Another way is to use public runtime variables
module.exports = {
publicRuntimeConfig: {
// Will be available on both server and client
port: 3500,
},
};
// Then
import getConfig from 'next/config';
// Only holds serverRuntimeConfig and publicRuntimeConfig
const { publicRuntimeConfig } = getConfig();
console.log(publicRuntimeConfig.port);
SEE: https://nextjs.org/docs/api-reference/next.config.js/runtime-configuration
But note that runtime config would impact optimization and you might get larger bundle in the end so you might want to try build time variables first.
if you get 404
i think that you reached out the server but no API's name matched
so try to test the API first on postman or alike
however if console.log('HOST: ', process.env.HOST);
prints http://localhost:3600 then do the following
in your .env file try to rename PORT TO SERVER_PORT or whatever
HOSTNAME=localhost
SERVER_PORT=3500
HOST=http://$HOSTNAME:$SERVER_PORT
i'm not sure but maybe ur frontend serve bash hold PORT env val as 3600
Related
I'm using the library next-csrf (https://github.com/j0lv3r4/next-csrf) to protect api routes in my next.js application.
I followed the documentation but the api now returns an error 500:
{"message":"Signed cookie string must be provided."}
Here's the code:
/lib/csrf.js:
import { nextCsrf } from 'next-csrf';
const options = {
secret: `${process.env.CSRF_SECRET}`,
};
export const { csrf, csrfToken } = nextCsrf(options);
Page that calls the api:
import { useState, useEffect } from 'react';
import axios from 'axios';
import { withRouter } from 'next/router';
import { Page, PostBlock } from '#/components';
const Main = ({ router, csrfToken }) => {
const [postsData, setPostsData] = useState({ posts: [], page: 0, pages: 0 });
function fetchData() {
axios
.get('/api/articles', {
headers: { 'XSRF-TOKEN': csrfToken },
params: {
page: router.query?.page,
lang: router.locale,
tag: router.query.tag,
},
})
.then(response => {
setPostsData(response.data);
})
.catch(error => console.log(error));
}
useEffect(() => {
fetchData();
}, []);
return (
<Page title='Home' className='home-template'>
<div id='grid' className='post-grid'>
{postsData.posts?.map(post => {=
return (
<PostBlock
featured={post.featured}
key={post.slug}
/>
);
})}
</div>
</Page>
);
};
export default withRouter(Main);
The token works and I can see the header in the network tab:
Api route:
import { getPosts } from '../../../utils/index';
import { csrf } from '../../../lib/csrf';
function handler(req, res) {
const {
query: { page, lang, tag },
method,
} = req;
switch (method) {
case 'GET':
const posts = getPosts(page, lang, tag);
res.status(200).json(posts);
break;
default:
break;
}
}
export default csrf(handler);
There's also another thing happenig. If I try to call the api from postman the api works. I can see that there's a cookie with the "XSRF-TOKEN" value inside that I haven't set in any way, so I'm not sure where Postman is getting it:
How can I fix this?
Such error message is possible only in the case when cookie value is not a string. And according to next-csrf getCookie code the cookie value may be not a string, only in case when there are some cookies but not the required one.
And there is an error in next-csrf transpilation which makes code from line 52 to move to the line 26, skipping some checks and changing the program logic. You can see it here https://unpkg.com/browse/next-csrf#0.1.2/dist/next-csrf.js, at the line 1891.
Now:
To avoid this case you should send first request without any cookie set up. It seems like this is what Postman does and this is why it works in Postman.
A fix in the code of getCookie is required. The function should return string, not string or undefined.
Also I wouldn't recommend you to use this library without proper transpilation issue resolution.
Postman receives cookie, because next-csrf middleware defines set-cookie header, if there is no one in the first request.
I decided to play a little bit with nuxt for the first time from scratch to finish.
and now, I am trying to add plugins.
the plugin I am trying to add is for my api. But when I inject it, it throws the error "inject is not a function". This is my code below. Every other thing works to the best of my knowledge.
import Vue from 'vue'
import axios from 'axios'
import get from 'lodash/get'
import cookies from 'js-cookie'
import { BASE_URL } from '../config/config'
export default (context, inject) => {
const saveToken = (token) => {
cookies.set('AuthToken', token)
}
const removeToken = () => {
cookies.remove('AuthToken')
}
const getToken = () => {
cookies.get('AuthToken')
}
const token = getToken() || ''
const config = {
baseURL: `${BASE_URL}/api/v1`,
params: {},
headers: {
Authorization: `Bearer ${token}`
}
}
const service = axios.create(config)
service.interceptors.response.use(
response => response,
(error) => {
// src of error.
const data = get(error, 'response.data', {})
Vue.$store.commit('notifications/setNotification', data)
}
)
const ApiService = {
...service,
removeToken,
saveToken
}
inject('ApiService', ApiService)
}
Okay, So, I was able to fix it.
Apparently, the error was caused because I added the plugin in the module array instead of the plugins array as suggested in the Nuxt docs.
After putting it in the plugins as opposed to putting it as a module like I did previously, my dev server has started working again.
I'm reconfiguring my NextJS/Apollo app to allow for SSG with GraphQL API routes, and I'm using this official NextJS starter example as a base for the client config.
I've run into an interesting issue though in my own app, so I've went back to starter example and tried to reproduce it, and was able to. The issue is that without any context object passed into the query resolvers, everything works fine (in the playground and on the client). However, when you introduce a context object and pass it to the resolvers, it works fine in the playground but the context object is undefined when fired from the client. This is the code from the official NextJS starter example, I'll comment where I've added anything.
graphql.js
import { ApolloServer } from "apollo-server-micro";
import { schema } from "../../apollo/schema";
const apolloServer = new ApolloServer({
schema,
context: { //
foo: "bar", // this is the context object I've added
}, //
});
export const config = {
api: {
bodyParser: false,
},
};
export default apolloServer.createHandler({ path: "/api/graphql" });
typedefs.js
import { gql } from '#apollo/client'
export const typeDefs = gql`
type User {
id: ID!
name: String!
status: String!
}
type Query {
viewer: User
}
`
schema.js
import { makeExecutableSchema } from 'graphql-tools'
import { typeDefs } from './type-defs'
import { resolvers } from './resolvers'
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
resolvers.js
export const resolvers = {
Query: {
viewer: (_parent, _args, context, _info) => {
console.log("context", context); // console log check that I've added
return { id: 1, name: "John Smith", status: "cached" };
},
},
};
When I run this in the GraphQL playground and query the API, it gives me the correct response, and in my terminal console it returns the context foo: bar object from the console log, so in the server the context object is being passed correctly. However, when I visit the index page in the browser, which is this:
index.js
import gql from "graphql-tag";
import Link from "next/link";
import { useQuery } from "#apollo/client";
import { initializeApollo } from "../apollo/client";
const ViewerQuery = gql`
query ViewerQuery {
viewer {
id
name
status
}
}
`;
const Index = () => {
const {
data: { viewer },
} = useQuery(ViewerQuery);
return (
<div>
You're signed in as {viewer.name} and you're {viewer.status} goto{" "}
<Link href="/about">
<a>static</a>
</Link>{" "}
page.
</div>
);
};
export async function getStaticProps() {
const apolloClient = initializeApollo();
await apolloClient.query({
query: ViewerQuery,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
},
};
}
export default Index;
...the viewer name and viewer status are rendered, so the query is actually happening, but in the console, the context object console log is returning undefined. So when used in the client, the context is being lost somehow. I find this interesting, since this is an official NextJS starter example, and unless they've set up the client to not accept context in the resolvers, I can't see what the problem is. And if it is the case that the client is not set up to accept context, is there any other official examples with a client setup that does?
This is a long question now, but here is the client.js setup:
import { useMemo } from "react";
import { ApolloClient, InMemoryCache } from "#apollo/client";
let apolloClient;
function createIsomorphLink() {
if (typeof window === "undefined") {
const { SchemaLink } = require("#apollo/client/link/schema");
const { schema } = require("./schema");
return new SchemaLink({ schema });
} else {
const { HttpLink } = require("#apollo/client/link/http");
return new HttpLink({
uri: "http://localhost:3000/api/graphql",
credentials: "same-origin",
});
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createIsomorphLink(),
cache: new InMemoryCache(),
});
}
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) {
_apolloClient.cache.restore(initialState);
}
// 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 useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
I implore anyone to clone this official repo and see if they can figure out how to get context working in the client, or if anyone knows why this client setup isn't working for context and knows a client setup that does accept resolver context, I would appreciate it. This problem has cost me two days now!
I've figured out the problem. The client configuration is using SchemaLink for the http request, and the context is passed in the SchemaLink constructor function, not in the server options, because context is passed in the http headers with httpLink.
I'm attempting to add an Axios plugin to Nuxt as described here, but it doesn't seem to work.
This is my plugins/axios.js file...
export default function({ $axios }) {
console.log('Im in the axios plugin')
$axios.defaults.baseURL = `https://localhost:5001/api`
$axios.defaults.headers = {
Accept: 'application/json',
'Content-Type': 'application/json'
}
$axios.onRequest((config) => {
console.log('Making request to ' + config.url)
})
}
This is my nuxt.config.js
plugins: ['~/plugins/axios'],
modules: ['#nuxtjs/axios']
And this is where I use Axios in a file called services/BookService.js:
import axios from 'axios'
export default {
getBooks() {
return axios.get('/Home')
},
getBooksFiltered(payload) {
return axios.post('/Home/Filters', payload)
}
}
I get the console.log('Im in the axios plugin') from within my plugin, but nothing else. $axios.onRequest doesn't appear to run, and the baseURL doesn't appear to be set correctly when getBooksFiltered is triggered. I get a 404 when it tried to hit the address http://localhost:3000/Home/Filters. As described in my plugin, the address should be https://localhost:5001/api/Home/Filters
I've also tried the following in my nuxt.config.js, but it doesn't work:
axios: {
baseURL: 'https://localhost:5001/api'
}
Any ideas?
Edit
I've modified my services/BookService.js based on the suggestion below to the following...
export default {
getBooks(axios) {
console.log('Im in getBooks')
return axios.get('/Home')
}
}
My action request that makes my api call is the following....
import BookService from '~/services/BookService.js'
export const fetchBooks = (context) => {
console.log('Im in fetchBooks action')
return BookService.getBooks(this.$axios)
.then((response) => {
context.commit('SET_BOOKS', response.data.booksList)
})
.catch((error) => {
console.log(error)
})
}
And my method in my component that calls the actions...
async fetch({ store, error }) {
try {
console.log('Im in index -> fetch')
await store.dispatch('fetchBooks')
} catch (e) {
error({
statusCode: 503,
message: 'Unable to fetch books at this time'
})
}
}
I'm aware that I may be mixing async/await with promises incorrectly but I don't believe it's the cause of this issue.
Console returns the following...
My network tab contains a single request to http://localhost:3000/ which seems incorrect. It should be https://localhost:5001/api/Home based on the plugin and the address specified in the action. It is also never entering $axios.onRequest
The axios-module sets up an Axios instance on the Nuxt app instance. When you import Axios from axios, and use it directly, you're not using the previously setup Axios instance.
To fix the issue, you could either reference the preconfigured Axios instance from window.$nuxt.$axios (only in the browser), or setup your service to take an Axios instance as a parameter:
// services/BookService.js
export default axios => ({
getBooks() {
return axios.get('/Home')
},
getBooksFiltered(payload) {
return axios.post('/Home/Filters', payload)
}
})
// store.js
import BookService from '~/services/BookService.js'
export default {
actions: {
async getBooks({ commit }) {
const books = await new BookService(this.$axios).getBooks()
commit('SET_BOOKS', books)
}
}
}
Another solution from nuxt-community/axios-module #28:
~/plugins/axios-port.js
import { setClient } from '~/services/apiClient'
export default ({ app, store }) => {
setClient(app.$axios)
}
~/services/apiClient.js
let client
export function setClient (newclient) {
client = newclient
}
// Request helpers
const reqMethods = [
'request', 'delete', 'get', 'head', 'options', // url, config
'post', 'put', 'patch' // url, data, config
]
let service = {}
reqMethods.forEach((method) => {
service[method] = function () {
if (!client) throw new Error('apiClient not installed')
return client[method].apply(null, arguments)
}
})
export default service
Use:
import apiClient from '~/services/apiClient'
export default {
async current () {
return apiClient.get('...')
}
}
In my case I exported a customized axios instance as the doc suggested in my axios.js
export default function ({ $axios }, inject) {
const api = $axios.create({
baseURL:'/api'
})
// Inject to context as $api
inject('api', api)
}
Then use this.$api.get or this.$api.post in your getBook service
The above one works for me
As I have just tested, in each request we should use $axios.
Example: this.$axios.get('....'), or in another context this.$nuxt.$axios.get('...');
Because axios extension use with the app context instance, if we import, it will create a new instance which plugin cannot extend.
I have put test code on stackblitz: here
It seems you need to yarn add #nuxtjs/axios or npm install #nuxtjs/axios like the setup instruction here before it can work: https://axios.nuxtjs.org/setup
I haven't experienced with nuxt yet but I don't think by adding some line of code into some js file without actually installing will make the package available into your repo.
I'm trying to figure out an appropriate way of doing authentication, which I know is a touchy subject on the GitHub issue page.
My authentication is simple. I store a JWT token in the session. I send it to a different server for approval. If I get back true, we keep going, if I get back false, it clears the session and puts sends them to the main page.
In my server.js file I have the following (note- I am using the example from nextjs learn and just adding isAuthenticated):
function isAuthenticated(req, res, next) {
//checks go here
//if (req.user.authenticated)
// return next();
// IF A USER ISN'T LOGGED IN, THEN REDIRECT THEM SOMEWHERE
res.redirect('/');
}
server.get('/p/:id', isAuthenticated, (req, res) => {
const actualPage = '/post'
const queryParams = { id: req.params.id }
app.render(req, res, actualPage, queryParams)
})
This works as designed. If I refresh the page /p/123, it will redirect to the /. However, if I go there via a next/link href, it doesn't. Which I believe is because it's not using express at this point but next's custom routing.
Is there a way I can bake in a check for every single next/link that doesn't go through express so that I can make sure the user is logged in?
Tim from the next chat helped me solve this. Solution can be found here but I will quote him so you all can see:
You can do the check in _app.js getInitialProps and redirect like this
Example of how to use it
_app.js documentation
I've also created an example skeleton template you can take a look at.
--
EDIT July 2021 - WARNING: This is an outdated solution and has not been confirmed to work with the latest versions of next.js. Use skeleton template at your own risk.
Edit: Updated answer for Next 12.2+
Note: The below contents is copied from the official blog post since SO generally discourages links that can become stale/dead over time
https://nextjs.org/blog/next-12-2#middleware-stable
Middleware is now stable in 12.2 and has an improved API based on feedback from users.
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
// If the incoming request has the "beta" cookie
// then we'll rewrite the request to /beta
export function middleware(req: NextRequest) {
const isInBeta = JSON.parse(req.cookies.get('beta') || 'false');
req.nextUrl.pathname = isInBeta ? '/beta' : '/';
return NextResponse.rewrite(req.nextUrl);
}
// Supports both a single value or an array of matches
export const config = {
matcher: '/',
};
Migration guide
https://nextjs.org/docs/messages/middleware-upgrade-guide
Breaking changes
No Nested Middleware
No Response Body
Cookies API Revamped
New User-Agent Helper
No More Page Match Data
Executing Middleware on Internal Next.js Requests
How to upgrade
You should declare one single Middleware file in your application, which should be located next to the pages directory and named without an _ prefix. Your Middleware file can still have either a .ts or .js extension.
Middleware will be invoked for every route in the app, and a custom matcher can be used to define matching filters. The following is an example for a Middleware that triggers for /about/* and /dashboard/:path*, the custom matcher is defined in an exported config object:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
// Supports both a single string value or an array of matchers
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
Edit: Outdated answer for next > 12 and < 12.2
With the release of Next.js 12, there's now beta support for middleware using Vercel Edge Functions.
https://nextjs.org/blog/next-12#introducing-middleware
Middleware uses a strict runtime that supports standard Web APIs like fetch. > This works out of the box using next start, as well as on Edge platforms like Vercel, which use Edge Functions.
To use Middleware in Next.js, you can create a file pages/_middleware.js. In this example, we use the standard Web API Response (MDN):
// pages/_middleware.js
export function middleware(req, ev) {
return new Response('Hello, world!')
}
JWT Authentication example
https://github.com/vercel/examples/tree/main/edge-functions/jwt-authentication
in next.config.js:
const withTM = require('#vercel/edge-functions-ui/transpile')()
module.exports = withTM()
in pages/_middleware.js:
import { NextRequest, NextResponse } from 'next/server'
import { setUserCookie } from '#lib/auth'
export function middleware(req: NextRequest) {
// Add the user token to the response
return setUserCookie(req, NextResponse.next())
}
in pages/api/_middleware.js:
import type { NextRequest } from 'next/server'
import { nanoid } from 'nanoid'
import { verifyAuth } from '#lib/auth'
import { jsonResponse } from '#lib/utils'
export async function middleware(req: NextRequest) {
const url = req.nextUrl
if (url.searchParams.has('edge')) {
const resOrPayload = await verifyAuth(req)
return resOrPayload instanceof Response
? resOrPayload
: jsonResponse(200, { nanoid: nanoid(), jwtID: resOrPayload.jti })
}
}
in pages/api/index.js:
import type { NextApiRequest, NextApiResponse } from 'next'
import { verify, JwtPayload } from 'jsonwebtoken'
import { nanoid } from 'nanoid'
import { USER_TOKEN, JWT_SECRET_KEY } from '#lib/constants'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'GET') {
return res.status(405).json({
error: { message: 'Method not allowed' },
})
}
try {
const token = req.cookies[USER_TOKEN]
const payload = verify(token, JWT_SECRET_KEY) as JwtPayload
res.status(200).json({ nanoid: nanoid(), jwtID: payload.jti })
} catch (err) {
res.status(401).json({ error: { message: 'Your token has expired.' } })
}
}
There is no middleware for no API routes in NextJS, but there are HOCs, which you can use to connect to db - select the user, etc:
https://hoangvvo.com/blog/nextjs-middleware