I am using Next.js's example server with Fastify and experimenting with it and am wondering if there is a way to pass let's say a JSON object as a prop into a render? I've tried to find anything in the documentation and can't find anything for doing this.
The server code I'm using is this,
const fastify = require('fastify')();
const Next = require('next');
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
fastify.register((fastify, opts, next) => {
const app = Next({ dev })
app.prepare().then(() => {
fastify.get('/', (req, res) => {
let object = {"hello": "world"}; // object I want to pass as a prop
return app.render(req.req, res.res, '/index', req.query).then(() => {
res.sent = true
})
})
next()
}).catch(err => next(err))
})
fastify.listen(port, err => {
if (err) throw err
console.log(`Ready on http://localhost:${port}`)
})
Your question is not specific to Fastify, but relevant for all server frameworks.
The basic idea is that req & res object are passed to Next's getInitialProps.
So you can put your data on them.
For example, express's Response object has locals attribute that is specific to this job.
So, in order to pass data attach it to req / res.
fastify.get('/', (req, res) => {
const object = { hello: 'world' }; // object I want to pass as a prop
res.res.myDataFromController = object;
return app.render(req.req, res.res, '/index', req.query).then(() => {
res.sent = true;
});
});
// some next page.jsx
const IndexPage = ({ dataFromGetInitilProps }) => (
<div> {JSON.stringify(dataFromGetInitilProps, null, 2)} </div>
);
IndexPage.getInitilProps = ctx => {
const { res } = ctx;
// res will be on the context only in server-side
const dataFromGetInitilProps = res ? res.myDataFromController: null;
return {
dataFromGetInitilProps: res.myDataFromController,
};
};
export default IndexPage;
Related
[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}`)
})
})
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
}
}
}
Below is an example of a link structure that I am trying to work with:
www.baseurl.com/pathname/some-sub-information
I essentially want NextJS to render the file matching the /pathname/ - so pathname.js. No matter what /some-sub-information might be, NextJS should render the pathname.js file, using the /some-sub-information as parameters for an API call.
I know this could essentially be done by passing queries through the link, and have it hook the the pathname, although I have been instructed by marketing that this is how they want the links.
I am at a bit of a loss how to do this, as this is the first time I am working with Next and SSR in general. I am hoping that there is someway in Next to specify that it should render a certain file when it hits the /pathname part of the url, and then just ignore the rest of the url.
This might be too much to ask, bug if there is any other way that I could achieve this, guiding information would be highly appreciated.
The solution I can think of is to add a custom server where you parse path like /pathname/some-sub-information and converts it into page to render pathname and some additional param some-sub-information
server.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; // pathname = '/pathname/some-sub-information'
const parts = pathname.split('/');
const page = parts[1]; // 'pathname'
const param = parts[2]; // 'some-sub-information'
if (page) {
const queryParams = { ...query, pageParam: param };
app.render(req, res, '/' + page, queryParams);
} else {
handle(req, res, parsedUrl);
}
}).listen(3000, err => {
if (err) throw err;
console.log('> Ready on http://localhost:3000');
});
});
Params that are passed from server to client app.render(req, res, '/' + page, { pageParam: 'test' }); can be accessed inside getInitialProps query param e.g. query.pageParam
So the page will look something like this
pages/index.js
function Index({ pageParam }) {
return (
<div>
INDEX component with {pageParam}
</div>
);
}
Index.getInitialProps = async ({ query }) => {
const { pageParam } = query;
return { pageParam };
};
export default Index;
Having this custom server and pages/index.js (node server.js), go to /index/some-data-here will result into the following page
Hope this helps you!
I am an aspiring react developer and I am having some issues with my app.
I am trying to scrape the New York times for an assignment I have, and I can get my data with a search to log server-side, but I cant seem to pass it back. I can get my data by pushing it to a MongoDB then querying it in a separate process from the front end, but I don't want to do that.
I want to pass the object back up the stack to the client side. Does anyone know how I might accomplish that?
here is some of my code.
my dir structure:
here is the client folder structure:
here is my Home.jsx file clientside in /pages:
import React, { Component } from 'react';
import { Container, Row, Column } from '../../components/BootstrapGrid';
import API from '../../utils/API'
import {Input, FormBtn} from '../../components/Form'
class Home extends Component {
state = {
formInput: "",
posts: [],
}
loadArticles = (res) => {
console.log('res')
}
handleInputChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
});
};
handleFormSubmit = event => {
event.preventDefault();
let query = this.state.formInput
// console.log(query)
API.scrapeArticles(query)
// console.log(this.state)
};
render() {
return (
<Container>
<Row>
<Column>
</Column>
<Column>
<Input
value={this.state.formInput}
onChange={this.handleInputChange}
name="formInput"
placeholder="Search Query (required)"
/>
<FormBtn onClick={this.handleFormSubmit}>Scrape NYT API</FormBtn>
</Column>
</Row>
</Container>
);
}
}
export default Home;
here is my code calling the clientside api in client/utils/api/:
import axios from "axios";
export default {
// getPosts: function () {
// return axios.get('/api/posts')
// },
// savePost: function (postData) {
// return axios.post("/api/posts", postData);
// },
scrapeArticles: function (query) {
// console.log(query)
let queryData = {
query: query
}
return axios.post('/api/scraper', queryData)
}
};
here is my code from the backend routes/index.js being hit by axios (i think? Im honestly not sure how but i think this is the flow):
const path = require("path");
const router = require("express").Router();
const postsController = require('../controllers/postsController')
router.route("/")
.get(postsController.getAll)
.post(postsController.create);
router.route('/api/scraper')
.post(postsController.scraper)
.get(postsController.scraper)
// If no API routes are hit, send the React app
router.use(function (req, res) {
res.sendFile(path.join(__dirname, "../client/build/index.html"));
});
module.exports = router;
here is my controller that is referenced in the file above:
const scraper = require('../scraper')
const db = require('../models');
module.exports = {
create: function (req, res) {
db.Post
.create(req.body)
.then(dbmodel => res.json(dbmodel))
.catch(err => res.status(422).json(err))
},
getAll: function (req, res) {
db.Post
.find(req.query)
.sort({date: -1})
.then(dbModel => res.json(dbModel))
.catch(err => res.status(422).json(err))
},
scraper: function (req, res) {
let queryData = req.body.query
scraper(queryData)
},
scraperGet: function (req, res) {
scraper()
console.log(res.body)
}
}
and lastly, here is the scraper file on the backend:
const request = require('request');
const mongoose = require('mongoose');
const db = require('./models');
const scraper = (queryData) => {
console.log(`#scraper ${queryData}`)
let articleData = []
request.get({
url: "https://api.nytimes.com/svc/search/v2/articlesearch.json",
qs: {
'api-key': "-----------------------------",
"q" : queryData
},
}, function (err, response, body) {
body = JSON.parse(body);
let articles = body.response.docs
articles.forEach(element => {
// console.log(element)
let title= element.headline.main
let url = element.web_url
let synopsis = element.abstract
let snippet = element.snippet
let source = element.source
let pubDate = element.pub_date
let article = {
title: title,
url: url,
synopsis: synopsis,
snippet: snippet,
source: source,
pubDate: pubDate,
}
// console.log(article)
articleData.push(article)
db.Post.create({title:article.title}).then(article => {
console.log(article)
}).catch(err => {
console.log(err)
})
});
return articleData
});
}
module.exports = scraper;
So i know right now it is pushing to mongo. This is only because I couldn't figure out how to pass that data back just stored in a variable.
I really don't want to have to push all my results to the db and then make a query for them. I want to have a save article function that you only save the ones you actually want.
You should send articleData to the client and then get it in the client side using .then() method of a promise.
Something like this:
scraper: function (req, res) {
let queryData = req.body.query
const articleData = scraper(queryData)
// return your json to the client
res.json(articleData)
},
Then you should receive this data in the client side, like this:
handleFormSubmit = event => {
event.preventDefault()
let query = this.state.formInput
API.scrapeArticles(query)
.then(resp => {
this.setState({ posts: resp })
})
}
I have an react app that I want to pass value down to a koa server.
let data = new FormData()
data.append('json', JSON.stringify(token))
fetch('/charge', { method: 'POST', body: data })
.then((res) => {
return res.json()
})
.then((json) => {
console.log('something wrong')
console.log(json)
})
and below is my server code
const config = require('../config')
const server = require('../server/main')
const router = require('koa-router')()
const parse = require("co-body")
const port = config.server_port
server.use(router.routes())
router
.post('/charge', function (ctx, next) {
console.log(ctx.request.body)
console.log('howyd')
ctx.body = "howdy"
})
Just can't get the value passing down from client. Do you guys know what is going on?
Make sure you're using the body parser. It looks like you're requiring it in, but not actually using it. Something like this (untested):
const config = require('../config')
const server = require('../server/main')
const router = require('koa-router')()
const parse = require("co-body")
const port = config.server_port
server.use(router.routes())
router
.post('/charge', async (ctx, next) => {
let body = await parser.json(ctx.request)
console.log(body)
console.log('howyd')
ctx.body = "howdy"
})