Losing query string by refreshing page in Nextjs using express - node.js

I have a problem with nextjs and none of solutions helped me!!! I, m trying to have a dynamic route in my SSR project, everything is working well in localhost, but when I try to upload it to the server or my IIS problem shows on
: The problem is this that everything works fine at first render from the server and after refreshing the page I lose my query string and Undefined error happens. Here are my codes:
Server.js:
const express = require("express");
const next = require("next");
const port = process.env.PORT || 3003;
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
server.get("/", (req, res) => {
return app.render(req, res, "/index");
});
server.get("/test?org&des&roomRules&departing&returning", (req, res) => {
return app.render(req, res, "/test" , {
org: req.query.org ,
des: req.query.des,
roomRules:req.query.roomRules,
departing: req.query.departing,
returning: req.query.returning,
});
});
server.get("*", (req, res) => {
return handle(req, res);
});
server.listen(port, err => {
if (err) throw err;
console.log(`READY ON http://localhost:${port}`);
});
});
Parent Component:
const RoundButton = props => {
const { searchParams , name} = props;
const URL2 = `/test?org=${searchParams.org}&des=${searchParams.des}&roomRules=${searchParams.roomRules}&departing=${searchParams.orgDate}&returning=${searchParams.desDate}`;
return (
<Link href={URL2} as={URL2}>
Test
</Link>
);
};
Test Componet :
function Test(props) {
return (
<div>
<h1>Test</h1>
<h2>roomrules :{props.queryStrings}</h2>
</div>
)
}
Test.getInitialProps = ({query}) => {
const queryStrings = query;
return {
queryStrings: queryStrings
};
};
export default Test
As you can see I used getInitialProps and it only works on first render.
And finally my scripts in package.json:
"scripts": {
"dev": "node server.js",
"build": "next build && next export",
"start": " node server.js"
},
To publish I write "npm run build" in cmd. Any solution?

As you are using the next js you can use the following approach to make the URL parameter persistent in refresh:
Assume you had a component named TestComponent:
TestComponent.getInitialProps = async (context: any) => {
const { oid } = context.query;
return { id: oid };
};
The oid is the parameter that you want to be persistent after page refresh and not be undefined.
export default function TestComponent(props: any) {
props.id
}
This is the approach that you are using in the source code, the other approach is as follow:
export default function TestComponent(props: any){
const router = useRouter();
const { oid } = router.query;
}
now you have access to the oid and for the useEffect function use the following approach:
export default function TestComponent(props: any){
const router = useRouter();
const { oid } = router.query;
useEffect(function(){
}, [oid])
}
with this setup, your code should also work on Refresh, consider if you have multiple calls to fetch data from the server you have to check that multiple requests precisely.

Related

Some APIs doesn't work In Next js on production

I have a project on Next js. It's working development correctly but when I deploy it some apis doesn't work.
backend api:
https://mutfakci.westhospital.az/api/blog
frontend blog:
https://front.westhospital.az/blog/1
blog page:
const Detail = ({ article }) => {
console.log(article);
return (
<section id="blog_detail">
<div className="container-fluid">
<div className="row">
<div className="col-lg-12 p-0">
<div className="dtail_img">
<img src="/FORWEB01 1 (1).png" alt="" />
</div>
</div>
</div>
</div>
<BlogDetailMain article={article} />
{/* <LatesBlog/> */}
<Coporation />
</section>
);
};
export default Detail;
export const getStaticProps = async (context) => {
const res = await fetch(`${server}/api/blog/${context.params.id}`);
const responseData = await res.json();
const article = responseData.data;
return {
props: {
article,
},
};
};
export const getStaticPaths = async () => {
const res = await fetch(`${server}/api/blog`);
const articlesResponse = await res.json();
const articles = articlesResponse.data;
const ids = articles.map((article) => article.id);
const paths = ids.map((id) => ({ params: { id: id.toString() } }));
return {
paths,
fallback: false,
};
};
My deploy file:- dep.js:
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const hostname = process.env.NODE_ENV !== "production" ? 'localhost' : 'front.westhospital.az'
const port = process.env.NODE_ENV || 5522
// when using middleware `hostname` and `port` must be provided below
const app = next({ dev, hostname, port })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer(async (req, res) => {
try {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
await app.render(req, res, '/a', query)
} else if (pathname === '/b') {
await app.render(req, res, '/b', query)
} else {
await handle(req, res, parsedUrl)
}
} catch (err) {
console.error('Error occurred handling', req.url, err)
res.statusCode = 500
res.end('internal server error')
}
}).listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://${hostname}:${port}`)
})
})
Next config file:
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
}
module.exports = nextConfig
package.json file:
"scripts": {
"dev": "node dep.js",
"build": "next build",
"start": "NODE_ENV=production node dep.js"
},
I want to render my pages correctly on production.
Thanks.

Installation Link Breaks in Production - Shopify App

EDIT: I believe the issue is that app.use(serveStatic(PROD_INDEX_PATH)); prevents the app.use("/*", middleware from running. I'm still working on a solution and any help would be appreciated!
My Shopify app breaks after deploying to Heroku but ONLY when clicking the provided "install" link.
For example, when I click "Install App", this is the URL that is generated: https://shopify-app.herokuapp.com/?hmac=d61597ca3ea6ca74b8bd6ea8f8bcc812b4382fad6f15434c0158bc4c3ade519a&host=dXBsaWZ0ZWQtY29tbWVyY2UtZGV2Lm15c2hvcGlmeS5jb20vYWRtaW4&shop=dev.myshopify.com&timestamp=1657326911
Which skips that auth process and takes me to "This page does not exist."
So the flow is: "Click Install App" => "This page does not exist"
However, if I manually click the auth link: https://shopify-app.herokuapp.com/api/auth?shop=dev.myshopify.com, the app successfully completes the auth process and works without any issues.
I'm using the standard server code scaffolded from the Shopify CLI.
Commenting out this section in production allows the app to partially function:
if (isProd) {
const compression = await import("compression").then(
({ default: fn }) => fn
);
const serveStatic = await import("serve-static").then(
({ default: fn }) => fn
);
app.use(compression());
app.use(serveStatic(PROD_INDEX_PATH));
}
Once the code is commented out, the generated install link is of the format: https://shopify-app.herokuapp.com/api/auth?shop=dev.myshopify.com. However, functionality throughout the app breaks upon clicking the install button.
The new flow is: "Click Install App" => "Begin Auth flow" => "Accept requested scopes" => "App breaks"
So essentially, the code snippet if(isProd) breaks the installation link while removing it breaks the rest of the app's functionality.
More specifically, the app.use(serveStatic(PROD_INDEX_PATH)); snippet.
Really scratching my head on this one. Any ideas what's going on?
This is my server:
const USE_ONLINE_TOKENS = true;
const TOP_LEVEL_OAUTH_COOKIE = "shopify_top_level_oauth";
// #ts-ignore
const PORT = parseInt(process.env.BACKEND_PORT || process.env.PORT, 10);
const isTest = process.env.NODE_ENV === "test" || !!process.env.VITE_TEST_BUILD;
const versionFilePath = "./version.txt";
let templateVersion = "unknown";
if (fs.existsSync(versionFilePath)) {
templateVersion = fs.readFileSync(versionFilePath, "utf8").trim();
}
// TODO: There should be provided by env vars
const DEV_INDEX_PATH = `${process.cwd()}/frontend/`;
const PROD_INDEX_PATH = `${process.cwd()}/frontend/dist/`;
const DB_PATH = `${process.cwd()}/database.sqlite`;
Shopify.Context.initialize({
// #ts-ignore
API_KEY: process.env.SHOPIFY_API_KEY,
// #ts-ignore
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
// #ts-ignore
SCOPES: process.env.SCOPES.split(","),
// #ts-ignore
HOST_NAME: process.env.HOST.replace(/https?:\/\//, ""),
// #ts-ignore
HOST_SCHEME: process.env.HOST.split("://")[0],
API_VERSION: ApiVersion.April22,
IS_EMBEDDED_APP: true,
// This should be replaced with your preferred storage strategy
// SESSION_STORAGE: new Shopify.Session.SQLiteSessionStorage(DB_PATH),
SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
storeCallback,
// #ts-ignore
loadCallback,
deleteCallback
),
USER_AGENT_PREFIX: `Node App Template/${templateVersion}`,
});
const ACTIVE_SHOPIFY_SHOPS = {};
Shopify.Webhooks.Registry.addHandler("APP_UNINSTALLED", {
path: "/api/webhooks",
webhookHandler: async (topic, shop, body) => {
// #ts-ignore
delete ACTIVE_SHOPIFY_SHOPS[shop],
//Delete unsubscribed shop and clean undefined entries
console.log("APP UNINSTALLED");
await pool.query(
`DELETE FROM shop WHERE shop_url=$1 OR shop_url='undefined' OR shop_url='' OR shop_url IS NULL`,
[shop]
);
},
});
setupGDPRWebHooks("/api/webhooks");
// export for test use only
export async function createServer(
root = process.cwd(),
isProd = process.env.NODE_ENV === "production",
billingSettings = BILLING_SETTINGS
) {
const app = express();
app.set("top-level-oauth-cookie", TOP_LEVEL_OAUTH_COOKIE);
app.set("active-shopify-shops", ACTIVE_SHOPIFY_SHOPS);
app.set("use-online-tokens", USE_ONLINE_TOKENS);
app.use(cookieParser(Shopify.Context.API_SECRET_KEY));
applyAuthMiddleware(app, {
billing: billingSettings,
});
app.post("/api/webhooks", async (req, res) => {
try {
await Shopify.Webhooks.Registry.process(req, res);
console.log(`Webhook processed, returned status code 200`);
} catch (error) {
console.log(`Failed to process webhook: ${error}`);
if (!res.headersSent) {
res.status(500).send(error.message);
}
}
});
app.use(bodyParser.json());
// All endpoints after this point will require an active session
app.use(
"/api/*",
verifyRequest(app, {
// #ts-ignore
billing: billingSettings,
})
);
//app.use("/api/test", test);
app.use("/api/sort-options", sortOptions);
app.use("/api/sort-logic", sortLogic);
app.get("/api/products-count", async (req, res) => {
const session = await Shopify.Utils.loadCurrentSession(req, res, true);
const { Product } = await import(
`#shopify/shopify-api/dist/rest-resources/${Shopify.Context.API_VERSION}/index.js`
);
const countData = await Product.count({ session });
res.status(200).send(countData);
});
app.post("/api/graphql", async (req, res) => {
try {
const response = await Shopify.Utils.graphqlProxy(req, res);
res.status(200).send(response.body);
} catch (error) {
res.status(500).send(error.message);
}
});
app.use(express.json());
app.use((req, res, next) => {
const shop = req.query.shop;
if (Shopify.Context.IS_EMBEDDED_APP && shop) {
res.setHeader(
"Content-Security-Policy",
`frame-ancestors https://${shop} https://admin.shopify.com;`
);
} else {
res.setHeader("Content-Security-Policy", `frame-ancestors 'none';`);
}
next();
});
if (isProd) {
const compression = await import("compression").then(
({ default: fn }) => fn
);
const serveStatic = await import("serve-static").then(
({ default: fn }) => fn
);
app.use(compression());
app.use(serveStatic(PROD_INDEX_PATH));
console.log(`Serving static files from ${PROD_INDEX_PATH}`);
}
app.use("/*", async (req, res, next) => {
const shop = req.query.shop;
console.log("THIS IS THE CATCHALL ROUTE")
// //CHECK TO MAKE SURE SCOPE EXISTS AND ISN'T UNDEFINED, OFFLINE SHOPS?
const shopValue = await pool.query(`SELECT * FROM shop WHERE shop_url=$1`, [
shop,
]);
if (shopValue?.rows[0]?.scope) {
ACTIVE_SHOPIFY_SHOPS[shop] = shopValue.rows[0].scope;
} else {
ACTIVE_SHOPIFY_SHOPS[shop] = undefined;
}
// Detect whether we need to reinstall the app, any request from Shopify will
// include a shop in the query parameters.
// #ts-ignore
if (app.get("active-shopify-shops")[shop] === undefined) {
res.redirect(`/api/auth?shop=${shop}`);
} else {
// res.set('X-Shopify-App-Nothing-To-See-Here', '1');
const fs = await import("fs");
console.log(`Serving static files from ${DEV_INDEX_PATH}`);
const fallbackFile = join(
isProd ? PROD_INDEX_PATH : DEV_INDEX_PATH,
"index.html"
);
res
.status(200)
.set("Content-Type", "text/html")
.send(fs.readFileSync(fallbackFile));
}
});
return { app };
}
The solution is changing: app.use(serveStatic(PROD_INDEX_PATH)); to app.use(serveStatic(PROD_INDEX_PATH, { index: false }));
It was a bug in the CLI which was resolved here

why is axios fetch not working on mobile?

I have the following setup which works on my PC but doesn't on mobile even when there is data to fetch.
useEffect(() => {
const {username, room} = queryString.parse(location.search);
setRoom(room);
if (messages.length > 3) {
let lastMessage = messages.pop();
setMessss([lastMessage]);
const fetchHistory = async () => {
try {
const result = await axios.get(`https://example.com/messages/${room}`,);
setMessss(result.data.messagesFromAPI);
} catch (error) {
console.log(error);
}
};
fetchHistory();
}
}, [messages]);
I also have another useEffect hook that works on PC on componentDidMount but doesn't work if I reload the page more than once but I want it to work on every page reload but it doesn't fetch...
could this be because I use the free subscription (M0) on Mongodb Atlas? Although from metrics my database hasn't exhausted or reached capacity.
useEffect(() => {
const {username, room} = queryString.parse(location.search);
// setRoom(room);
axios.get(`https://example.com/messages/${room}`)
.then(response => {
const history = response.data.messagesFromAPI;
setMessss(history);
})
.catch(error => {
console.log(error.response);
});
},[]);
Here's how I solved it
In the src folder just add a file called
setupProxy.js
and write this code inside
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
["/api/*",], // the base api route you can change it
createProxyMiddleware({
target: "http://localhost:4000", // the local server endpoint
})
);
};
make sure to change the target port to the port where the server is running.
For some reason axios does not behave properly locally.
You can add the setupProxy.js to .gitignore

Next JS Custom Server app.render do not pass query to a component

I'm trying to pass userData with app.render, but while Server side rendering router.query is empty, although i have passed userData! Is it NextJS's bug, or am i doing something wrong?
app.js:
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/index') {
app.render(req, res, '/index', {
userData: {
id: 1,
name: 'admin'
}
})
} else {
handle(req, res, parsedUrl)
}
}).listen(3333, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
pages/index.js:
import { useRouter } from 'next/router'
export default () => {
const router = useRouter();
const { query } = router;
return (
<div>
Query: {JSON.stringify(query)}
</div>
);
};
If getInitialProps is absent, Next.js will statically optimize your
page automatically by prerendering it to static HTML. During
prerendering, the router's query object will be empty since we do not
have query information to provide during this phase. Any query values
will be populated client side after hydration.
You can access your query using getInitialProps.
with useRouter:
import { useRouter } from 'next/router'
const Index = () => {
const router = useRouter();
const { query } = router;
return (
<div>
Query: {JSON.stringify(query)}
</div>
);
};
Index.getInitialProps = async () => {
return {};
};
export default Index
with a class component:
import React from 'react'
class Index extends React.Component {
static async getInitialProps (context) {
let query = context.query;
return {query}
}
render() {
let {query} = this.props
return (
<div>
Query: {JSON.stringify(query)}
</div>
);
}
}
export default Index
Or if you prefer a functional component :
const Index = (props) => (
<div>
Query: {JSON.stringify(props.query)}
</div>
)
Index.getInitialProps = async ( context ) => {
let query = context.query;
return {query}
}
export default Index
Please note that obviously this works with /index but not with /
I think it's as simple as adding a return statement before app.render to prevent the rest of the code from executing.
if (pathname === '/index') {
return app.render(req, res, '/index', {
userData: {
id: 1,
name: 'admin'
}
})
} else {
handle(req, res, parsedUrl)
}

React app on Heroku cannot make a POST request

I+m playing with the Chatkit API, and when running a React app in my local machine everything seems to work fine, but when I pushed it to Heroku, every time it tries to do a POST request through the server, it gives Failed to load resource: net::ERR_CONNECTION_REFUSED and index.js:1375 error TypeError: Failed to fetch
This is my server.js
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const Chatkit = require('#pusher/chatkit-server')
const app = express()
const chatkit = new Chatkit.default({
instanceLocator: I HAVE MY INSTANCE LOCATOR HERE,
key: I HAVE MY KEY HERE,
})
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(cors())
app.post('/users', (req, res) => {
const { username } = req.body
chatkit
.createUser({
id: username,
name: username
})
.then(() => res.sendStatus(201))
.catch(error => {
if (error.error === 'services/chatkit/user_already_exists') {
res.sendStatus(200)
} else {
res.status(error.status).json(error)
}
})
})
app.post('/authenticate', (req, res) => {
const authData = chatkit.authenticate({ userId: req.query.user_id })
res.status(authData.status).send(authData.body)
})
const PORT = 3001
app.listen(PORT, err => {
if (err) {
console.error(err)
} else {
console.log(`Running on port ${PORT}`)
}
})
And then this is my App.js
import React, { Component } from 'react'
import UsernameForm from './components/UsernameForm'
import ChatScreen from './ChatScreen'
class App extends Component {
constructor() {
super()
this.state = {
currentUsername: '',
currentScreen: 'WhatIsYourUsernameScreen'
}
this.onUsernameSubmitted = this.onUsernameSubmitted.bind(this)
}
onUsernameSubmitted(username) {
fetch('http://localhost:3001/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username }),
})
.then(response => {
this.setState({
currentUsername: username,
currentScreen: 'ChatScreen'
})
})
.catch(error => console.error('error', error))
}
render() {
if (this.state.currentScreen === 'WhatIsYourUsernameScreen') {
return <UsernameForm onSubmit={this.onUsernameSubmitted} />
}
if (this.state.currentScreen === 'ChatScreen') {
return <ChatScreen currentUsername={this.state.currentUsername} />
}
}
}
export default App
I believe that it's at this time that it breaks
return <UsernameForm onSubmit={this.onUsernameSubmitted} />
When submitting it is expected to make a POST request to create a new user, and React to load the new component, but it just stays in the UsernameForm component, and in the console I can see these errors:
Failed to load resource: net::ERR_CONNECTION_REFUSED
index.js:1375 error TypeError: Failed to fetch
Probably the issue is the localhost in the endpoint at onUsernameSubmitted. We need more details about how your application is deployed and how the communication between server and spa is designed. If you have an Nginx you can set the redirect there.
I see three potential reasons of the error:
Database has to be well deployed and db:migrate triggered to define the db schema.
If 1) is fulfilled, then make sure whether your graphql path points to server url my-app.herokuapp.com not to localhost:<port>, The easiest way to check that is via browser/devtools/network query.
(optional) I use ApolloClient and my rule process?.env?.NODE_ENV ? 'prod_url' : 'dev_url' didn't work because of missing vars definitions in webpack:
new DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
},
}),```

Resources