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!
Related
I am learning how to design API and at the same time how to use next.js API route.
I have set my first route api/property/[propertyId] that returns the specific property detail.
Now I am trying to set a dynamic route for the specific property id in the page folder page/property/[propertyId]. My issue is when I am getting directed on the specific page the data is not there as expected. I am receiving a response for error message.
Can someone point out what I did wrong, please?
pages>api>property>[propertyId]
export default function propertyHandler ({query : {propertyId} } ,res) {
var parser = new xml2js.Parser({explicitArray : false});
const data = fs.readFileSync(path.join(process.cwd(),'listing.xml'))
parser.parseString(data,function (err, results){
results = results.client.secondhandListing.property
const filteredProp = results.filter((property) => property.id === propertyId)
filteredProp.length > 0 ? res.status(200).json(filteredProp[0]) : res.status(404).json({message: `Property with id: ${propertyId} not found.` })
})
}
pages>property>[id].js
export const getDetails = async() => {
const res = await fetch(`${baseUrl}/api/property/[property.Id]}`)
const data = res.json()
return data
}
export async function getServerSideProps({params: { id } }) {
const data = await getDetails(`${baseUrl}/api/property/${id}`)
return {
props: {
propertyDetails: data
}
}
}
I got the answer to my mistake from somewhere else. It was my getdetails function that was wrong.
I have amended it to:
export const getDetails = async(baseUrl)=>{
const res = await fetch(baseUrl)
const data = await res.json()
return data
};
and it worked.
So I have this blog, with dynamic url for posts (blog/[postId]). Posts are being added through keystone.js cms. It worked fine via CSR, but i came to a necessity to add proper meta open graph tags for each post (for correct share snippets).
I added getStaticProps and getStaticPaths to my post page, and now my meta tags are working fine, but now i get 404 when trying to access any post, that was added after build. Meaning, now i have to rebuild after each new post. My question is - is it possible to set Next to render component as CSR, if it fails to find path for it with getStaticPaths? I've tried to use try catch, but that didn't work. Now i came up with this concept of using container, but it does not seem to be the right way to use fallback. Or is it? I am fairly new to Next, so i am a bit confused.
const PostContainer = (props) => {
const [postData, setPostData] = React.useState(null);
const router = useRouter();
React.useEffect(() => {
if (router.query?.id) {
getPost();
}
}, [router.query]);
const getPost = async () => {
const postData = await getPost(`${router.query.id}`);
if (postData) {
setPostData(postData);
}
};
if (router.isFallback) {
return <Post data={postData}
metaProps={defaultMetaProps}
/>
}
return (
<Post data={postData}
metaProps={props}
/>
);
};
export const getStaticPaths = async () => {
const list = await getPosts();
if (list?.length) {
const paths = list.map((post) => {
return {
params: {
id: post.id,
},
};
});
return {
paths,
fallback: true,
};
}
return {
paths: [],
fallback: true,
};
};
export const getStaticProps = async ({ params }) => {
const { id } = params;
const post = await getPost(id);
const {title, description, previewImage} = post
const props = {
props: {
title,
description,
url: `https://sitename./blog/${id}`,
image: previewImage,
},
revalidate: 1,
};
return props
};
export default PostContainer;
And one more thing - do i understand correctly that if fallback option is true, it takes only one visit to the page by ANY user, after which new path is generated on the server permanently and is accessible for anyone who comes next, therefore fallback always fires just once for this first visit of first user?
I'm creating a global function that checks whether the jwt token is expired or not.
I call this function if I'm fetching data from the api to confirm the user but I'm getting the error that I cannot update during an existing state transition and I don't have a clue what it means.
I also notice the the if(Date.now() >= expiredTime) was the one whose causing the problem
const AuthConfig = () => {
const history = useHistory();
let token = JSON.parse(localStorage.getItem("user"))["token"];
if (token) {
let { exp } = jwt_decode(token);
let expiredTime = exp * 1000 - 60000;
if (Date.now() >= expiredTime) {
localStorage.removeItem("user");
history.push("/login");
} else {
return {
headers: {
Authorization: `Bearer ${token}`,
},
};
}
}
};
I'm not sure if its correct but I call the function like this, since if jwt token is expired it redirect to the login page.
const config = AuthConfig()
const productData = async () => {
const { data } = await axios.get("http://127.0.0.1:5000/product", config);
setProduct(data);
};
I updated this peace of code and I could login to the application but when the jwt expires and it redirect to login using history.push I till get the same error. I tried using Redirect but its a little slow and I could still navigate in privateroutes before redirecting me to login
// old
let expiredTime = exp * 1000 - 60000;
if (Date.now() >= expiredTime)
// change
if (exp < Date.now() / 1000)
i would start from the beginning telling you that if this is a project that is going to production you always must put the auth token check in the backend especially if we talk about jwt authentication.
Otherwise if you have the strict necessity to put it in the React component i would suggest you to handle this with Promises doing something like this:
const config = Promise.all(AuthConfig()).then(()=> productData());
I would even consider to change the productData function to check if the data variable is not null before saving the state that is the reason why the compiler is giving you that error.
const productData = async () => {
const { data } = await axios.get("http://127.0.0.1:5000/product", config);
data && setProduct(data);
};
Finally consider putting this in the backend. Open another question if you need help on the backend too, i'll be glad to help you.
Have a nice day!
I'm still not sure how your code is used within a component context.
Currently your API and setProduct are called regardless whether AuthConfig() returns any value. During this time, you are also calling history.push(), which may be the reason why you encountered the error.
I can recommend you to check config for value before you try to call the API.
const config = AuthConfig()
if (config) {
const productData = async () => {
const { data } = await axios.get("http://127.0.0.1:5000/product", config);
setProduct(data);
};
}
I'm assuming that AuthConfig is a hook, since it contains a hook. And that it's consumed in a React component.
Raise the responsibility of redirecting to the consumer and try to express your logic as effects of their dependencies.
const useAuthConfig = ({ onExpire }) => {
let token = JSON.parse(localStorage.getItem("user"))["token"];
const [isExpired, setIsExpired] = useState(!token);
// Only callback once, when the expired flag turns on
useEffect(() => {
if (isExpired) onExpire();
}, [isExpired]);
// Check the token every render
// This doesn't really make sense cause the expired state
// will only update when the parent happens to update (which
// is arbitrary) but w/e
if (token) {
let { exp } = jwt_decode(token);
let expiredTime = exp * 1000 - 60000;
if (Date.now() >= expiredTime) {
setIsExpired(true);
return null;
}
}
// Don't make a new reference to this object every time
const header = useMemo(() => !isExpired
? ({
headers: {
Authorization: `Bearer ${token}`,
},
})
: null, [isExpired, token]);
return header;
};
const Parent = () => {
const history = useHistory();
// Let the caller decide what to do on expiry,
// and let useAuthConfig just worry about the auth config
const config = useAuthConfig({
onExpire: () => {
localStorage.removeItem("user");
history.push("/login");
}
});
const productData = async (config) => {
const { data } = await axios.get("http://127.0.0.1:5000/product", config);
setProduct(data);
};
useEffect(() => {
if (config) {
productData(config);
}
}, [config]);
};
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;
I am using Next.js (with Redux, react-i18next, styled components and Express) and Next.js loads my pages without AJAX (with a hard load, no in-place content replacement). Unfortunately, I can't determine the issue of the problem. There's no error in the console (browser and server). Does anyone of you know how to debug this issue or has helpful tips about the problem?
Here's the code of my server:
const express = require('express');
const next = require('next');
const {parse} = require('url');
const i18nextMiddleware = require('i18next-express-middleware');
const Backend = require('i18next-node-fs-backend');
const i18n = require('../hoc/i18n');
const port = parseInt(process.env.APP_PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({dev});
const handle = app.getRequestHandler();
// init i18next with server-side settings
// using i18next-express-middleware
i18n.use(Backend).use(i18nextMiddleware.LanguageDetector).init({
preload: ['en', 'de'],
ns: ['common', 'home', 'content'],
backend: {
loadPath: __dirname + '/../locales/{{lng}}/{{ns}}.json',
addPath: __dirname + '/../locales/{{lng}}/{{ns}}-missing.json',
jsonIndent: 2
}
}, () => {
app.prepare().then(() => {
const server = express();
// Translation routing
server.use(i18nextMiddleware.handle(i18n));
server.use('/locales', express.static(__dirname + '/../locales'));
server.post('/locales/add/:lng/:ns', i18nextMiddleware.missingKeyHandler(i18n));
// Application Routing
server.get('*', (req, res) => {
// 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.startsWith('/_next')) {
return handle(req, res, parsedUrl);
} else if (pathname === '/') {
return app.render(req, res, '/', query);
} else {
return app.render(req, res, '/content', query);
}
});
server.listen(port, err => {
if (err) {
throw err;
}
console.log(`> Application server ready on http://localhost:${port}`);
});
})
});
The link itself is created with
<Link href={item.link}>
<a>
{item.title}
</a>
</Link>
Even though the server maps the dynamic URL correctly, the client-side still has to use the following link syntax to make it work (especially important is the "as" attribute):
<Link href={'/content'} as={'/the-real-url'}>
<a>Test Link</a>
</Link>