Access masked URLs in next.js - reactjs

I have a page called "Channels", the final url should look like messages/:channelName, the following Link partially solves the problem:
<Link key={ name }
prefetch href={ `/channel?channel=${name}` }
as={`/messages/${name}`} >
Problem is, if i directly type this masked URL on the browser i get a 404, i can't refresh the page nor use the return button on the browser. I know this can be solved by creating these routes in the server and referring to the correct pages, but i'm trying to do this using only Next.js, is it possible?

Ofcourse you can do that by only use Next.js.
package.json
{
"name": "custom-server",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
},
"dependencies": {
"next": "latest",
"react": "^16.7.0",
"react-dom": "^16.7.0"
}
}
server.js
const { createServer } = require('http')
const { parse } = require('url')
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(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/messages/:name') {
app.render(req, res, '/channel', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(port, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
channel.js
import React from "react";
export default (prop) => {
return <div>channel {prop.url.query.channel}</div>;
}

Related

Why my app keeps crashing on Heroku after deployment? I keep getting a H10 Error

I've tried to make this happen for a while and things aren't going my way.
index.js
var express = require('express');
var app = express();
var cors = require('cors');
let mongodb = require('mongodb');
//require('dotenv').config();
//console.log(process.env);
var dal = require('./dal.js');
// used to serve static files from public directory
app.use(express.static('public'));
app.use(cors());
app.use((req,res,next)=>{
console.log('Time:', Date.now())
next()
})
// create user account
app.post('/account/create/:name/:email/:password', function (req, res) {
// create user
dal.create(req.params.name,req.params.email,req.params.password).
then((user) => {
console.log(user);
res.send(user);
});
}
);
// login user
app.get('/account/login/:email/:password', function (req, res) {
dal.find(req.params.email).
then((user) => {
// if user exists, check password
if(user.length > 0){
if (user[0].password === req.params.password){
res.send(user[0]);
}
else{
res.send({error:'Login failed: wrong password'});
}
}
else{
res.send({error:'Login failed: user not found'});
}
});
});
// find user account
app.get('/account/find/:email', function (req, res) {
dal.find(req.params.email).
then((user) => {
console.log(user);
res.send(user);
});
});
// find one user by email - alternative to find
app.get('/account/findOne/:email', function (req, res) {
dal.findOne(req.params.email).
then((user) => {
console.log(user);
res.send(user);
});
});
// update - deposit/withdraw amount
app.get('/account/update/:email/:amount', function (req, res) {
var amount = Number(req.params.amount);
dal.update(req.params.email, amount).
then((response) => {
console.log(response);
res.send(response);
});
});
// all accounts
app.get('/account/all', function (req, res) {
dal.all().
then((docs) => {
console.log(docs);
res.send(docs);
});
});
//var port = process.env.PORT || 3000;
//app.listen(port, function () {
// console.log('Server is running on port: ' + port);
const host = '0.0.0.0';
var port = process.env.PORT || 8000;
app.listen(PORT,host, () => {
console.log(`Our app is running on port ${ PORT }`);
});
dal.js
require('dotenv').config();
console.log(process.env);
const { MongoClient, ServerApiVersion } = require('mongodb');
const url ='mongodb://localhost:27017';
//const url=process.env.MONGODB_URI;
let db = null;
MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }, function(err, client) {
console.log("Connected successfully to db server");
// connect to my badbank database
db = client.db('myproject');
collection = db.collection("users");
});
// create user account
function create(name, email, password) {
return new Promise((resolve, reject) => {
const collection = db.collection('users');
const doc = {name, email, password, balance: 0};
collection.insertOne(doc, {w:1}, function(err, result) {
err ? reject(err) : resolve(doc);
});
});
}
// find user account
function find(email){
return new Promise((resolve, reject) => {
const customers = db
.collection('users')
.find({email: email})
.toArray(function(err, docs) {
err ? reject(err) : resolve(docs);
});
})
}
// find user account
function findOne(email){
return new Promise((resolve, reject) => {
const customers = db
.collection('users')
.findOne({email: email})
.then((doc) => resolve(doc))
.catch((err) => reject(err));
})
}
// update - deposit/withdraw amount
function update(email, amount){
return new Promise((resolve, reject) => {
const customers = db
.collection('users')
.findOneAndUpdate(
{email: email},
{ $inc: { balance: amount}},
{ returnOriginal: false },
function (err, documents) {
err ? reject(err) : resolve(documents);
}
);
});
}
// all users
function all() {
return new Promise((resolve, reject) => {
const customers = db
.collection('users')
.find({})
.toArray(function(err, docs) {
err ? reject(err) : resolve(docs);
});
});
}
module.exports = {create, findOne, find, update, all};
mongo_test.js
const MongoClient = require('mongodb').MongoClient;
const url = 'mongodb://localhost:27017';
// connect to mongo
MongoClient.connect(url, {useUnifiedTopology: true}, function(err, client) {
console.log("Connected successfully to server");
// database Name
const dbName = 'fullstackBadBankDB';
const db = client.db(dbName);
// new user
var name = 'user' + Math.floor(Math.random()*10000);
var email = name + '#mit.edu';
// insert into customer table
var collection = db.collection('customers');
var doc = {name, email};
collection.insertOne(doc, {w:1}, function(err, result) {
console.log('Document insert');
});
var customers = db
.collection('customers')
.find()
.toArray(function(err, docs) {
console.log('Collection:',docs);
// clean up
client.close();
});
});
package.json
{
"name": "badbankfullstack",
"version": "1.0.0",
"description": "## Description This project was made to showcase everything I have learned while attending MIT xPro and to encapslate the learnings into one project. This is a three tier application of a bank with very little security, although it does have a frontend that was built using JS, CSS, HTML, and React.JS. The Backend is compose with Node.js, Express, MongoDB, and Firebase.",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.1",
"firebase": "^9.10.0",
"lowdb": "^3.0.0",
"mongodb": "^4.10.0",
"node": "^18.10.0",
"node.js": "^0.0.1-security",
"serve": "^14.0.1"
}
}
Procfile
web: node index.js
.env
root=3000
MONGODB_URI=mongodb://localhost:27017
I keep coming up on dead ends, not sure what to do any more.
What I am trying to accomplish is to upload/deploy my three tier application to Heroku and I've read over the documentation and still not working.

Heroku ReactJS app. Home route is functional, but when I try another route it shows up as 404 Not Found

I'm trying to make a sport/tinder like app for a school project from a friend of mine. It came together well on my localhost, but for him it was a requirement to host it online. Not really a professional in hosting, but I was a bit familiar with Heroku. I used a client and a server side for my application, so I build the client side and put it into the server side folder. This server side is hosted on the Heroku page and shows the front page of my application. But whenever I try to login, it won't work and I get this error message in my console.
​GET https://[app].herokuapp.com/dashboard 404 (Not Found)
And the Heroku webpage says this.
Cannot GET /dashboard
I know there are a lot of other people with the same issue. I tried many solutions and I think something might be wrong with my routing. I'm very new to ReactJS so I don't really know how the routing e.g. work. And I'm more like a Data Scientist then a Software Engineer, but I'm always eager to learn that's why I took the oppurtunity to learn this new 'language'. So I might be wrong when it comes to the problem.
This is the function I use to login with. This function is on my client side so I'm not sure if it transfers when I build to my server side.
const handleSubmit = async (e) => {
e.preventDefault()
try {
if( isSignUp && ( password !== confirmPassword)) {
setError('Passwords need to match!')
return
}
const response = await axios.post(`https://[app].herokuapp.com/${isSignUp ? 'signup' : 'login'}`, { email, password })
setCookie('AuthToken', response.data.token)
setCookie('UserId', response.data.userId)
const success = response.status === 201
if (success && isSignUp) navigate('/onboarding')
if (success && !isSignUp) navigate('/dashboard')
window.location.reload()
} catch (error) {
console.log(error)
}
}
The header of my index.js server
const PORT = process.env.PORT || 8000
const express = require('express')
const {MongoClient} = require('mongodb')
const {v1: uuidv4} = require('uuid')
const jwt = require('jsonwebtoken')
const cors = require('cors')
const bcrypt = require('bcrypt')
require('dotenv').config()
const uri = process.env.URI
const app = express()
app.use(cors())
app.use(express.json())
app.use(express.static(__dirname + "/public"));
app.get('/', (req, res) => {
res.json('Hello to my app')
})
The signup from my index.js server
app.post('/signup', async (req, res) => {
const client = new MongoClient(uri)
const {email, password} = req.body
const generateUserId = uuidv4()
const hashedPassword = await bcrypt.hash(password, 10)
try {
await client.connect()
const database = client.db('app-data')
const users = database.collection('users')
const existingUser = await users.findOne({email})
if (existingUser) {
return res.status((409).send('User already exists. Please login'))
}
const sanitizedEmail = email.toLowerCase()
const data = {
user_id: generateUserId,
email: sanitizedEmail,
hashed_password: hashedPassword
}
const insertedUser = await users.insertOne(data)
const token = jwt.sign(insertedUser, sanitizedEmail, {
expiresIn: 60 * 24,
})
res.status(201).json({token, userId: generateUserId})
} catch (err) {
console.log(err)
} finally {
await client.close()
}
})
The login from my index.js server
app.post('/login', async (req, res) => {
const client = new MongoClient(uri)
const {email, password} = req.body
try {
await client.connect()
const database = client.db('app-data')
const users = database.collection('users')
const user = await users.findOne({email})
const correctPassword = await bcrypt.compare(password, user.hashed_password)
if (user && correctPassword) {
const token = jwt.sign(user, email, {
expiresIn: 60 * 24
})
res.status(201).json({token, userId: user.user_id})
}
res.status(400).send('Invalid Credentials')
} catch (err) {
console.log(err)
} finally {
await client.close()
}
})
Any help is welcome. I hope I included enough code examples, reply if you need more ;)
EDIT
My package.json file from the server directory.
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"start:backend": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
My package.json file from the client directory.
{
"name": "funfit",
"homepage": "https://[app].herokuapp.com/",
"version": "0.1.0",
"private": true,
"dependencies": {
"#testing-library/jest-dom": "^5.16.2",
"#testing-library/react": "^12.1.4",
"#testing-library/user-event": "^13.5.0",
"axios": "^0.26.1",
"react": "^17.0.2",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-router-dom": "^6.2.2",
"react-scripts": "5.0.0",
"react-tinder-card": "^1.4.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build",
"start:frontend": "./node_modules/.bin/react-scripts start",
"build": "./node_modules/.bin/react-scripts build",
"test": "./node_modules/.bin/react-scripts test",
"eject": "./node_modules/.bin/react-scripts eject"
},
This is my server's configurations
/* import */
const path = require("path");
app.use(express.static(__dirname + "/../client/build"));
app.get("*", (req, res) => {
// send index.html
let indexPath = path.join(__dirname, "/../client/build/index.html")
res.sendFile(indexPath);
});
See if that works. I think the issue with your code is only home route gets sent since only "/" given, but not sure.
Make sure your dirname points to your build directory I see as it is right now it points to public only

FetchError: invalid json response body while deploying the Nextjs application on a server [duplicate]

I don't understand these errors when I export as production npm run build , but when I test npm run dev it works just fine. I use getStaticProps and getStaticPath fetch from an API route.
First when I npm run build
FetchError: invalid json response body at https://main-website-next.vercel.app/api/products reason: Unexpected token T in JSON at position
0
at D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:272:32
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async getStaticPaths (D:\zummon\Main Website\main-website-next\.next\server\pages\product\[slug].js:1324:18)
at async buildStaticPaths (D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:16:80)
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:26:612
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\tracer.js:1:1441 {
type: 'invalid-json'
}
\pages\product\[slug]
import { assetPrefix } from '../../next.config'
export default function Page(){...}
export const getStaticProps = async ({ params: { slug }, locale }) => {
const res = await fetch(`${assetPrefix}/api/products/${slug}`)
const result = await res.json()
const data = result.filter(item => item.locale === locale)[0]
const { title, keywords, description } = data
return {
props: {
data,
description,
keywords,
title
}
}
}
export const getStaticPaths = async () => {
const res = await fetch(`${assetPrefix}/api/products`)
const result = await res.json()
const paths = result.map(({ slug, locale }) => ({ params: { slug: slug }, locale }))
return {
fallback: true,
paths,
}
}
next.config.js
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
assetPrefix: isProd ? 'https://main-website-next.vercel.app' : 'http://localhost:3000',
i18n: {
localeDetection: false,
locales: ['en', 'th'],
defaultLocale: 'en',
}
}
API routes
// pages/api/products/index.js
import data from '../../../data/products'
export default (req, res) => {
res.status(200).json(data)
}
// pages/api/products/[slug].js
import db from '../../../data/products'
export default ({ query: { slug } }, res) => {
const data = db.filter(item => item.slug === slug)
if (data.length > 0) {
res.status(200).json(data)
} else {
res.status(404).json({ message: `${slug} not found` })
}
}
// ../../../data/products (data source)
module.exports = [
{ locale: "en", slug: "google-sheets-combine-your-cashflow",
title: "Combine your cashflow",
keywords: ["Google Sheets","accounting"],
description: "...",
},
...
]
Second when I remove the production domain, I run npm run build but still get the error like
TypeError: Only absolute URLs are supported
at getNodeRequestOptions (D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1305:9)
at D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1410:19
at new Promise (<anonymous>)
at fetch (D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1407:9)
at getStaticPaths (D:\zummon\Main Website\main-website-next\.next\server\pages\[slug].js:938:21)
at buildStaticPaths (D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:16:86)
at D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:26:618
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\tracer.js:1:1441 {
type: 'TypeError'
}
My next.config.js after remove
const isProd = process.env.NODE_ENV === 'production'
module.exports = { //remove
assetPrefix: isProd ? '' : 'http://localhost:3000',
i18n: {
localeDetection: false,
locales: ['en', 'th'],
defaultLocale: 'en',
}
}
My package.json when I npm run build script
{
"name": "main-website-next",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "next start"
},
"dependencies": {
"next": "10.0.6",
"react": "17.0.1",
"react-dom": "17.0.1"
}
}
You should not call an internal API route inside getStaticProps. Instead, you can safely use your API logic directly in getStaticProps/getStaticPaths. These only happen server-side so you can write server-side code directly.
As getStaticProps runs only on the server-side, it will never run on
the client-side. It won’t even be included in the JS bundle for the
browser, so you can write direct database queries without them being
sent to browsers.
This means that instead of fetching an API route from
getStaticProps (that itself fetches data from an external source),
you can write the server-side code directly in getStaticProps.
Furthermore, your API routes are not available during build-time, as the server has not been started at that point.
Here's a small refactor of your code to address the issue.
// /pages/product/[slug]
import db from '../../../data/products'
// Remaining code..
export const getStaticProps = async ({ params: { slug }, locale }) => {
const result = db.filter(item => item.slug === slug)
const data = result.filter(item => item.locale === locale)[0]
const { title, keywords, description } = data
return {
props: {
data,
description,
keywords,
title
}
}
}
export const getStaticPaths = async () => {
const paths = db.map(({ slug, locale }) => ({ params: { slug: slug }, locale }))
return {
fallback: true,
paths,
}
}

Stripe JS: Status 200 OK, but payments don't show up in Stripe Payments Dashboard

I'm trying to integrate Stripe JS to my React ecommerce project. I'm currently able to see logs of the API calls successfully (Status 200 OK), but nothing is showing up in the Payments dashboard. Do you know what I need to do to see the Payments dashboard get updated?
To note, I know I'm not currently passing shipping and product details/totals. I'm assuming that any payment/product order details would show up as "null" in the payments dashboard, but I will fix this as the next step.
server.js
const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const postCharge = require('./stripe')
require('dotenv').config()
const app = express()
const router = express.Router()
const port = process.env.PORT || 7000
router.post('/stripe/charge', postCharge)
router.all('*', (_, res) =>
res.json({ message: 'please make a POST request to /stripe/charge' })
)
app.use((_, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
)
next()
})
app.use(bodyParser.json())
app.use('/api', router)
app.use(express.static(path.join(__dirname, '../build')))
app.get('*', (_, res) => {
res.sendFile(path.resolve(__dirname, '../build/index.html'))
})
app.listen(port, () => console.log(`server running on port ${port}`))
stripe.js
const stripe = require('stripe')('<MY_SECRET_KEY>')
async function postCharge(req, res) {
try {
const { amount, source, receipt_email } = req.body
const charge = await stripe.charges.create({
amount,
currency: 'usd',
source,
receipt_email
})
if (!charge) throw new Error('charge unsuccessful')
res.status(200).json({
message: 'charge posted successfully',
charge
})
} catch (error) {
res.status(500).json({
message: error.message
})
}
}
module.exports = postCharge
package.json
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "parcel build public/index.html --out-dir build --no-source-maps",
"dev": "node src/server.js & parcel public/index.html",
"start": "node src/server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"stripe": "^8.135.0"
}
}
CheckoutForm.jsx
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import {
CardNumberElement,
CardExpiryElement,
CardCVCElement,
injectStripe
} from 'react-stripe-elements'
import axios from 'axios'
import './CheckoutForm.scss'
const CheckoutForm = ({ selectedProduct, stripe, history }) => {
if (selectedProduct === null) history.push('/')
const [receiptUrl, setReceiptUrl] = useState('')
const handleSubmit = async event => {
event.preventDefault()
const { token } = await stripe.createToken()
// const order = await axios.post('http://localhost:7000/api/stripe/charge', {
// amount: selectedProduct.price.toString().replace('.', ''),
// source: token.id,
// receipt_email: 'customer#example.com'
// })
const order = await axios.post('http://localhost:7000/api/stripe/charge', {
amount: 200,
source: token.id,
receipt_email: 'customer#example.com'
})
if (receiptUrl) {
return (
<div className="success">
<h2>Payment Successful!</h2>
<a href={receiptUrl}>View Receipt</a>
<Link to="/">Home</Link>
</div>
)
}
return (
<div className="checkout-form">
<p>Amount: ${selectedProduct.price}</p>
<form onSubmit={handleSubmit}>
<label>
Card details
<CardNumberElement />
</label>
<label>
Expiration date
<CardExpiryElement />
</label>
<label>
CVC
<CardCVCElement />
</label>
<button type="submit" className="order-button">
Pay
</button>
</form>
</div>
)
}
export default injectStripe(CheckoutForm)
Response data screenshot when I click "Pay"

NextJS throws error on Firebase Functions Deploy

TLDR; I'm following an example from NextJS using Firebase, and with minimal change I can't push to Firebase.
I am following the NextJS with-firebase-hosting-and-typescript example, and in accordance with the help from #8893.
I changed the deploy script in package.json to cross-env NODE_ENV=production firebase deploy.
I also changed the conf value in functions/index.ts to
conf: {
distDir: `${path.relative(process.cwd(), __dirname)}/../functions/next`
}
When I go to deploy the app to firebase I now receive an error
Deployment error.
Error setting up the execution environment for your function. Please try deploying again after a few minutes.
I did some debugging and if I comment out the line
const app = next({ dev, conf: { distDir: `${path.relative(process.cwd(), __dirname)}/../functions/next` }
})
in functions/index.ts, then the functions will deploy just fine. So, the issue seems to be with next()
Here is code of the functions/index.ts, this throws the error.
import * as functions from 'firebase-functions'
import next from 'next'
import * as path from 'path'
const appSetup = {
dev: process.env.NODE_ENV !== 'production',
conf: { distDir: `${path.relative(process.cwd(), __dirname)}/../functions/next` }
}
console.log("appSetup: ", appSetup)
const app = next(appSetup)
// const handle = app.getRequestHandler()
export const nextApp = functions.https.onRequest(async(req, res) => {
// return app.prepare().then(() => handle(req, res))
return res.send({ status: "Hello from Firebase!, nextApp" })
})
Here is code of the functions/index.ts, this DOES NOT throw an error
import * as functions from 'firebase-functions'
import next from 'next'
import * as path from 'path'
const appSetup = {
dev: process.env.NODE_ENV !== 'production',
conf: { distDir: `${path.relative(process.cwd(), __dirname)}/../functions/next` }
}
console.log("appSetup: ", appSetup)
// const app = next(appSetup)
// const handle = app.getRequestHandler()
export const nextApp = functions.https.onRequest(async(req, res) => {
// return app.prepare().then(() => handle(req, res))
return res.send({ status: "Hello from Firebase!, nextApp" })
})
in package.json
"firebase-admin": "^8.10.0",
"firebase-functions": "^3.6.0",
"next": "^9.3.5",
"react": "16.13.1",
"react-dom": "16.13.1"
For anyone struggling with the same issue. The fix was the line in functions/index.ts
I needed to replace
conf: { distDir: `${path.relative(process.cwd(), __dirname)}/../functions/next` }
to
conf: { distDir: `${path.relative(process.cwd(), __dirname)}/next` }

Resources