NextJS rewrites with wildcard query string params - next.js

I am having a bit of an issue: I am trying to setup a rewrite in NextJS that will automatically send on the query string to the destination. However, the only examples I can find are with named params. In this case there could be any number of params, so I need a way of making a wildcard (I assume this will be possible?)
What I am looking to do is the following:
/results?param1=a&param2=b... => https://www.somedomain.com/results?param1=a&param2=b...
or
/results?house=red&car=blue&money=none => https://www.somedomain.com/results??house=red&car=blue&money=none
rewrites() {
return [
{
source:'/results?:params*',
destination:'https://www.somedomain.com/results?:params*'
},
Of course this doesn't work, so I looked into has but I cannot workout how to make it work without names params
{
source: '/some-page',
destination: '/somewhere-else',
has: [{ type: 'query', key: 'overrideMe' }],
},

You can use a standard page like this below. What I did is when the router is initialized in a useEffect hook it gets the query (object) and then use the objectToQueryString function to turn it back into a url encoded string, then used the router to redirect to the other page
// /pages/some-page.jsx
import { useRouter } from 'next/router'
import { useEffect } from 'react'
const objectToQueryString = (initialObj) => {
const reducer = (obj, parentPrefix = null) => (prev, key) => {
const val = obj[key];
key = encodeURIComponent(key);
const prefix = parentPrefix ? `${parentPrefix}[${key}]` : key;
if (val == null || typeof val === 'function') {
prev.push(`${prefix}=`);
return prev;
}
if (['number', 'boolean', 'string'].includes(typeof val)) {
prev.push(`${prefix}=${encodeURIComponent(val)}`);
return prev;
}
prev.push(Object.keys(val).reduce(reducer(val, prefix), []).join('&'));
return prev;
};
return Object.keys(initialObj).reduce(reducer(initialObj), []).join('&');
};
const Results = () => {
const router = useRouter()
useEffect(() => {
const query = router.query
router.replace(`https://www.somedomain.com/results?${objectToQueryString(query)}`)
}, [router])
return <></>
}
export default Results
I used the objectToQueryString based on Query-string encoding of a Javascript Object

To achieve this behavior, you need to capture the value of the query-parameter like this:
rewrites() {
return [
{
source: "/some-page",
has: [{ type: "query", key: "override_me", value: "(?<override>.*)" }],
destination: "/somewhere-else?override_me=:override",
},
];
}

By default, a rewrite will pass through any query params that the source URL may have.
In your case, you can simply have the following rewrite rule. All query params will be forwarded through the destination URL.
rewrites() {
return [
{
source: '/results',
destination: 'https://www.somedomain.com/results'
}
]
}

Related

getStatic Path not working for base URL "/" in NextJS

I'm using Prismic and NextJS for the first time.
What I'm trying to is make it so when the user goes to the base url for the page localhost:3000/ in dev something will load. /About and /Pricing are working fine the base url doesn't work.
import { GetStaticPaths, GetStaticProps } from 'next'
import { SliceZone } from '#prismicio/react'
import * as prismicH from "#prismicio/helpers";
import { createClient, linkResolver } from '../../prismicio'
import { components } from '../../slices'
interface HomePageProps {
page: any
}
const HomePage = (props:HomePageProps) => {
return <SliceZone slices={props.page.data.slices} components={components} />
}
export default HomePage
interface HomePageStaticProps {
params: any
previewData:any
}
export async function getStaticProps(props:HomePageStaticProps) {
console.log("DOES NOT FIRE FOR localhost:3000")
const client = createClient({ previewData:props.previewData })
const params = props.params;
const uid = params?.pagePath?.[params.pagePath.length - 1] || "home";
const page = await client.getByUID("page", uid);
return {
props: {
page,
},
}
}
export const getStaticPaths: GetStaticPaths = async () => {
const client = createClient();
const pages = await client.getAllByType("page");
const paths = pages.map((page) => prismicH.asLink(page, linkResolver)) as string[];
console.log(paths) // [ '/pricing', '/about', '/' ]
return {
paths,
fallback: false,
};
}
or to simplify it further
[[...pagePath]].tsx fails when going to localhost:3000/ but does not fail on localhost:3000/about or localhost:3000/pricing.
import { GetStaticPaths, GetStaticProps } from 'next'
interface HomePageProps {
page: string
}
const HomePage = (props:HomePageProps) => {
return <>{props.page}</>
}
export default HomePage
interface HomePageStaticProps {
params: any
previewData:any
}
export async function getStaticProps(props:HomePageStaticProps) {
const params = props.params;
const uid = params?.pagePath?.[params.pagePath.length - 1] || "home";
//const page = await client.getByUID("page", uid);
return {
props: {
page:uid,
},
}
}
export const getStaticPaths: GetStaticPaths = async () => {
const paths = [ '/pricing', '/about', '/' ];
return {
paths,
fallback: false,
};
}
As far as I can see your'e not fetching the right way. In order to to have a clean project I would recommend to use a const var where you can determine between dev and production enviorenment. To do so you can simply create a file for example: constants.js containing the following:
export const baseurl = process.env.NODE_ENV === "production"
? process.env.NEXT_PUBLIC_DOMAIN // <-- your domain on live
: "http://localhost:3000"; // localhost on dev
Now with this you automatically have localhost on your dev. Notice that you need http:// which your'e missing at the moment. Now the next example shows you how to fetch something on your root / by entering the following code:
import { baseurl } from "../utils/constants"; // <-- importing the constant
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export async function getStaticProps() {
// Call an external API endpoint to get posts.
// You can use any data fetching library
const res = await fetch(`${baseurl}/api/posts`)
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
If you are using Create-T3-App
https://github.com/t3-oss/create-t3-app
then
your next.config.mjs will default to this as of Nov 7, 2022
const config = {
reactStrictMode: true,
swcMinify: true,
i18n: {
locales: ["en"],
defaultLocale: "en",
},
};
export default config;
remove
const config = {
reactStrictMode: true,
swcMinify: true,
//i18n: {
// locales: ["en"],
// defaultLocale: "en",
//},
};
export default config;
This will make default "/" pathing work, if you require i18n, I'm sorry I can't help.

What is the different for UseGetSomeQuery Arg Value

I'm new for the RTK Query for redux.
What's the different for auto generated hook in below two ways.
The first way look like correct from the docs but it return 304 network status.
Second way, return 200. working perfectly
1.
const ProjectsList = () => {
const {
data: projects,
isLoading,
isSuccess,
isError,
error,
} = useGetProjectsQuery("projectList") // -- return 304 network status
}
worked fine. but cannot retrieve the object from the store. return.
const {
data: projects,
isLoading,
isSuccess,
isError,
error,
} = useGetProjectsQuery() // -- return 200 network status
Third, the memoized return uninitialize. It seem didn't correct.
// ApiSlice status return uninitialize
import { createSelector, createEntityAdapter } from "#reduxjs/toolkit"
import { apiSlice } from "#/app/api/apiSlice"
const projectsAdapter = createEntityAdapter({})
export const projectsApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getProjects: builder.query({
query: () => "/api/projects",
validateStatus: (response, result) => {
return response.status === 200 && !result.isError
},
transformResponse: (responseData) => {
const loadedProjects = responseData.map((project) => {
project.id = project._id
return project
})
return projectsAdapter.setAll(initialState, loadedProjects)
},
providesTags: (result, error, arg) => {
if (result?.ids) {
return [
{ type: "Project", id: "LIST" },
...result.ids.map((id) => ({ type: "Project", id })),
]
} else return [{ type: "Project", id: "LIST" }]
},
}),
}),
})
export const {
useGetProjectsQuery,
} = projectsApiSlice
export const selectProjectsResult =
projectsApiSlice.endpoints.getProjects.select()
// creates memoized selector
const selectProjectsData = createSelector(
selectProjectsResult,
(projectsResult) => {
console.log("projectsResult: ", projectsResult) // -> { isUninitialized: true, status: "uninitialize" }
return projectsResult.data
}
)
export const {
selectAll: selectAllProjects,
selectById: selectProjectById,
selectIds: selectProjectIds,
} = projectsAdapter.getSelectors(
(state) => selectProjectsData(state) ?? initialState
)
Since your query function is just query: () => "/api/projects" (so, not using the argument in any way), both will make exactly the same request for the same resource.
There is no difference between them and every difference you see is probably something random happening on the server and not bound to either invocation.
As for retrieving from the store, there is a difference however.
Your code
export const selectProjectsResult =
projectsApiSlice.endpoints.getProjects.select()
creates a selector for the cache entry that is created calling useGetProjectsQuery() - if you wanted the cache entry for useGetProjectsQuery("foo"), that would need to be projectsApiSlice.endpoints.getProjects.select("foo").
Please note that there should almost never be any reason to use those selectors with React components - those are an escape hatch if you are not working with React. If you are working with React, use the useGetProjectsQuery hook with selectFromResult.
I am seeing people use select in this fashion quite often recently and I assume this traces back to a tutorial that misunderstood the feature - did you learn that in a tutorial and could you share that tutorial? Maybe I can convince the author to change that part.

stitchSchema returns null

I have stitched two schemas together and run on localhost to query it. But the query returns null for the data in the second schema and I am not sure why.
I have the following code to stitch to remote schemas together and run a localhost graphql server to serve it. It should add the linked data from the second schema under cmsMetaData in the main Product data. But cmsMetaData is null.
import { ApolloServer } from 'apollo-server-micro';
import { ApolloServerPluginInlineTraceDisabled, ApolloServerPluginLandingPageLocalDefault } from "apollo-server-core";
import { stitchSchemas } from '#graphql-tools/stitch';
import { delegateToSchema } from '#graphql-tools/delegate';
import { RenameTypes, RenameRootFields } from '#graphql-tools/wrap';
import createRemoteSchema from '../../utils/createRemoteExecutor';
// Configuration for Next.js API Routes
export const config = {
api: {
bodyParser: false,
},
};
// Export as a Next.js API Route
export default async (req, res) => {
// Setup subschema configurations
const productsSubschema = await createRemoteSchema({
url: 'https://schema1.com/graphql/'
});
const cmsSubschema = await createRemoteSchema({
url: 'https://schema2.com/graphql/',
transforms: [
new RenameRootFields(
(operationName, fieldName, fieldConfig) => `strapi_${fieldName}`,
),
new RenameTypes((name) => `Strapi_${name}`),
],
});
// Build the combined schema and set up the extended schema and resolver
const schema = stitchSchemas({
subschemas: [productsSubschema, cmsSubschema],
typeDefs: `
extend type Product {
cmsMetaData: Strapi_Product
}
`,
resolvers: {
Product: {
cmsMetaData: {
selectionSet: `{ id }`,
resolve(product, args, context, info) {
// Get the data for the extended type from the subschema for Strapi
return delegateToSchema({
schema: cmsSubschema,
operation: 'query',
fieldName: 'strapi_product',
args: { where: { SaleorID: product.id } },
context,
info,
});
},
},
},
},
});
// Set up the GraphQL server
const apolloServer = new ApolloServer({
schema,
plugins: [
ApolloServerPluginInlineTraceDisabled(),
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
],
});
await apolloServer.start();
const apolloServerHandler = apolloServer.createHandler({
path: '/api/graphql',
});
// Return the GraphQL endpoint
return apolloServerHandler(req, res);
};
utils/createRemoteExecutor.js is:
import { introspectSchema, wrapSchema } from '#graphql-tools/wrap';
import { print } from 'graphql';
// Builds a remote schema executor function,
// customize any way that you need (auth, headers, etc).
// Expects to recieve an object with "document" and "variable" params,
// and asynchronously returns a JSON response from the remote.
export default async function createRemoteSchema({ url, ...filters }) {
const executor = async ({ document, variables }) => {
const query = print(document);
const fetchResult = await fetch(url, {
method: 'POST',
headers: {
// We can also do Authentication here
'Content-Type': 'application/json',
},
body: JSON.stringify({ query, variables }),
});
return fetchResult.json();
};
return wrapSchema({
schema: await introspectSchema(executor),
executor,
...filters,
});
}
The query is:
products(first: 100, channel: "default-channel")
{
edges
{
node
{
id
name
cmsMetaData
{
Title
SaleorID
}
}
}
}
In my api.tsx, which I generate using codegen.yaml, Product contains cmsMetaData as follows, which is of type Strapi_Product:
export type Product = Node & ObjectWithMetadata & {
__typename?: 'Product';
...
cmsMetaData?: Maybe<Array<Maybe<Strapi_Product>>>;
...
}
Strapi_Product is as follows which contains Title, SaleorID etc.:
export type Strapi_Product = {
__typename?: 'Strapi_Product';
SaleorID?: Maybe<Scalars['String']>;
Title?: Maybe<Scalars['String']>;
createdAt?: Maybe<Scalars['Strapi_DateTime']>;
publishedAt?: Maybe<Scalars['Strapi_DateTime']>;
updatedAt?: Maybe<Scalars['Strapi_DateTime']>;
};
But the date in GraphQL shows null for cmsMetaData as null:
{
"data": {
"products": {
"edges": [
{
"node": {
"id": "UHJvZHVjdDoxMjc=",
"name": "52-00 Base Plate",
"cmsMetaData": null
}
},
{
"node": {
"id": "UHJvZHVjdDoxMjg=",
"name": "52-01HD Weigh Module",
"cmsMetaData": null
}
}
]
}
}
}
Your problem seems related to this github issue. Most of your code looks totally fine, so I guess it fetches the schema correctly. The cmsMetaData field is null because it did not find anything matching objects using the selection criteria. This unwanted behavior is in the transformation and/or the resolver.
A good starting point for debugging would be to remove the RenameRootFields mutation. Furthermore, this example looks like your use case, it is an excellent step-by-step guide.
I also rebuild your example from an example I found on the internet. You most probably have a typo in one of the field names, that caused a null value for me. Make sure A equals B in the code below. I am guessing the initial fieldName is "Product", so after the transformation, this does not equal "strapi_product" and returns a null value.
const cmsSubschema = await createRemoteSchema({
url: 'https://schema2.com/graphql/',
transforms: [
new RenameRootFields(
(_, fieldName) => `strapi_${fieldName}`,), // A
new RenameTypes((name) => `Strapi_${name}`),
],
});
// Build the combined schema and set up the extended schema and resolver
const schema = stitchSchemas({
subschemas: [productsSubschema, cmsSubschema],
typeDefs: `
extend type Product {
cmsMetaData: Strapi_Product
}
`,
resolvers: {
Product: {
cmsMetaData: {
selectionSet: `{ id }`,
resolve(product, args, context, info) {
// Get the data for the extended type from the subschema for Strapi
return delegateToSchema({
schema: cmsSubschema,
operation: 'query',
fieldName: 'strapi_product', // B
args: { where: { SaleorID: product.id } },
context,
info,
});
},
},
},
},
});

NextJS: getServerSideProps - Is there a problem to use props object with redirect in the same return?

I'm using Next.js with Typescript, and I'm having some troubles to correctly type my props that getServerSideProps would return me. On getServerSideProps, or as I call it, getServerSidePropsImpl, I check user authentication and decide if I give it a redirect or the data for it to initialize. The problem with this is that Typescript doesn't correctly type my props, or give me some errors.
So, i had the idea to :
// /pages/sheet/1.tsx
import React from 'react';
import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import database from '../../utils/database';
import { sessionSSR } from '../../utils/session';
export default function Sheet1(props: InferGetServerSidePropsType<typeof getServerSidePropsImpl>): JSX.Element {
//...Do some stuff...
return <></>;
}
async function getServerSidePropsImpl(ctx: GetServerSidePropsContext) {
const player = ctx.req.session.player;
if (!player) {
return {
redirect: {
destination: '/',
permanent: false
},
props: {
playerID: 0,
playerInfo: []
}
};
}
const playerID = player.id;
const results = await Promise.all([
database.playerInfo.findMany({
where: {
player_id: playerID
},
select: {
info: true,
value: true
},
})
]);
return {
props: {
playerID,
playerInfo: results[0]
}
};
}
export const getServerSideProps = sessionSSR(getServerSidePropsImpl);
To make sure Typescript would correctly type my props object, I had to put generic values when returning a redirect:
return {
redirect: {
destination: '/',
permanent: false
},
props: {
playerID: 0,
playerInfo: []
}
};
Is there any issues to this approach, giving the amount of props will increase as I make this component?

Redux state and component property undefined until ajax resolves

My component get some properties via props with the function:
const mapStateToProps = state => {
const { entities: { keywords } } = state
const {locale} = state
return {
keywords: keywords[locale]
}
}
I got state keywords using ajax, in the same component:
componentDidMount() {
this.props.loadKeywords()
}
My component gets rendered twice. First, before the ajax resolves, so in my render method I got undefined:
render() {
const { keywords } = this.props.keywords
...
Which is the proper way to solve it? I changed componentDidMount to componentWillMount without success.
Right now, based on the real-world example, I have initialized keywords state with an empty object:
function entities(state = { users: {}, repos: {}, keywords: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
My reducer:
import { combineReducers } from 'redux'
import { routerReducer as router } from 'react-router-redux'
import merge from 'lodash/merge'
import locale from './modules/locale'
import errorMessage from './modules/error'
import searchText from './modules/searchText'
// Updates an entity cache in response to any action with response.entities.
function entities(state = { users: {}, repos: {}, keywords: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
export default combineReducers({
locale,
router,
searchText,
errorMessage,
entities
})
My action:
import { CALL_API, Schemas } from '../middleware/api'
import isEmpty from 'lodash/isEmpty'
export const KEYWORDS_REQUEST = 'KEYWORDS_REQUEST'
export const KEYWORDS_SUCCESS = 'KEYWORDS_SUCCESS'
export const KEYWORDS_FAILURE = 'KEYWORDS_FAILURE'
// Fetches all keywords for pictos
// Relies on the custom API middleware defined in ../middleware/api.js.
function fetchKeywords() {
return {
[CALL_API]: {
types: [ KEYWORDS_REQUEST, KEYWORDS_SUCCESS, KEYWORDS_FAILURE ],
endpoint: 'users/56deee9a85cd6a05c58af61a',
schema: Schemas.KEYWORDS
}
}
}
// Fetches all keywords for pictograms from our API unless it is cached.
// Relies on Redux Thunk middleware.
export function loadKeywords() {
return (dispatch, getState) => {
const keywords = getState().entities.keywords
if (!isEmpty(keywords)) {
return null
}
return dispatch(fetchKeywords())
}
}
All based on the Real world redux example
My Solution
Given initial state to keywords entity. I'm getting json like this through ajax:
{'locale': 'en', 'keywords': ['keyword1', 'keyword2']}
However as I use normalizr with locale as id, for caching results, my initial state is as I describe in the reducer:
function entities(state = { users: {}, repos: {}, keywords: { 'en': { 'keywords': [] } } }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
What I don't like is the initial if we have several languages, also remembering to modify it if we add another language, for example fr. In this
keywords: { 'en': { 'keywords': [] } }
should be:
keywords: { 'en': { 'keywords': [] }, 'fr': { 'keywords': [] } }
This line looks problematic:
const { keywords } = this.props.keywords
It's the equivalent of:
var keywords = this.props.keywords.keywords;
I doubt that's what you intended.
Another thing worth checking is keywords[locale] in your mapStateToProps() which will probably initially resolve to undefined. Make sure your component can handle that, or give it a sensible default.

Resources