Next.js Dynamic URL with _error - reactjs

[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}`)
})
})

Related

Dynamic generated sitemap -there are only declared pages on sitemap

I'd like to generate dynamic url fo each slug, but there is an array only with pages which I declared: const pages = ["/", "/about", "/portfolio", "/blog"];
http://localhost:3000/api/my-sitemap. I've installed npm sitemap from https://www.npmjs.com/package/sitemap
my query in ../../lib/data.js
export const getBlogSlugs = async () => {
const endpoint =
"https://api-eu-central-gsagasgasxasasxsaxasxassaster";
const graphQLClient = new GraphQLClient(endpoint);
const query = gql`
{
posts {
slug
}
}
`;
return await graphQLClient.request(query);
};
pages/api/my-sitemap.js
import { getBlogSlugs } from "../../lib/data";
const { SitemapStream, streamToPromise } = require("sitemap");
const { Readable } = require("stream");
export const getStaticProps = async () => {
const { posts } = await getBlogSlugs();
return {
props: {
posts,
},
};
};
export default async (req, res, posts) => {
try {
const links = [];
posts?.map((slug) => {
links.push({
url: `/blog/${slug}`,
changefreq: "daily",
priority: 0.9,
});
});
// Add other pages
const pages = ["/", "/about", "/portfolio", "/blog"];
pages.map((url) => {
links.push({
url,
changefreq: "daily",
priority: 0.9,
});
});
// Create a stream to write to
const stream = new SitemapStream({
hostname: `https://${req.headers.host}`,
});
res.writeHead(200, {
"Content-Type": "application/xml",
});
const xmlString = await streamToPromise(
Readable.from(links).pipe(stream)
).then((data) => data.toString());
res.end(xmlString);
} catch (e) {
console.log(e);
res.send(JSON.stringify(e));
}
};
I added to my robots.txt in pudblic folder:
User-agent: *
Allow: /
Sitemap: http://localhost:3000/api/my-sitemap
What I got is only declared pages
localhost:3000/api/my-sitemap
I tried like this and doesn't work too:
export const getStaticProps = async () => {
const data = await getBlogSlugs();
return {
props: {
posts: data.posts,
},
};
};
export default async (req, res, posts) => {
try {
const links = [];
posts?.map((post) => {
links.push({
url: `/blog/${post.slug}`,
changefreq: "daily",
priority: 0.9,
});
});
You cannot use getStaticProps from an API route.
https://github.com/vercel/next.js/discussions/16068#discussioncomment-703870
You can fetch the data directly inside the API function.
Edit: In my app, I use the API route code below to fetch data from external server
import fetch from "isomorphic-unfetch";
export default async (req, res) => {
try {
const result = await fetch("YOUR_URL");
const posts = await result.json();
//use posts
});
} catch (e) {}
};
For GraphQl may be you can check the example given in vercel site
https://github.com/vercel/next.js/tree/canary/examples/api-routes-graphql

Pass value from server side to client side in Next JS using getInitialProps

I'm building a application using nextJS.
In server/index.ts, I have :
expressApp.get('/', (req: express.Request, res: express.Response) => {
const parsedUrl = parse(req.url, true);
const { query } = parsedUrl;
let username: string | undefined;
if (process.env.STAGE !== 'local') {
username = getUsername(req)?.toString();
}
return nextApp.render(req, res, '/', {...query, loggedInUser: username});
});
and in my home page (path is '/'), I auto direct to path '/pageone' by doing:
const Home = () => {
const router = useRouter();
useEffect(() => {
router.push('/pageone', undefined, { shallow: true });
}, []);
return <PageOne />;
};
Home.getInitialProps = async (ctx: NextPageContext): Promise<{ username : string | string[] }> => {
const query = ctx.query;
return {
username: query.loggedInUser? query.loggedInUser : 'testUser'
};
};
export default Home;
I need this username variable in every page I build, how can I pass it to every page(for example pageone)?
You can attach this username variable to the res.locals which this is it's purpose.
Then access the res.locals of the getInitialProps ctx.
// server.ts
// this express middleware will attach username to res.locals.username;
expressApp.use((req, res, next) => {
res.locals = res.locals || {};
if (process.env.STAGE !== 'local') {
res.locals.username = getUsername(req)?.toString();
}
next();
});
expressApp.get('/', (req: express.Request, res: express.Response) => {
const parsedUrl = parse(req.url, true);
const {query} = parsedUrl;
return nextApp.render(req, res, '/', query);
});
Then instead of passing this value to the client, as you did, you can redirect from server side within getInitialProps.
// Home.tsx
const Home = () => {
return <PageOne />;
};
Home.getInitialProps = async ({res}: NextPageContext): Promise<{ username : string | string[] }> => {
if(res.locals.username === 'bla') {
return res.redirect(301, '/pageone');
// -----------^ will redirect at server side.
}
};
export default Home;
I've made a module that might help. 👍
Next Coke API allows typed communication between client and server:
Server:
// define API methods
const routes = {
getName: async (body) => {
return "your name is " + body.name
}
}
// export types to the client
export type AppRoutes = typeof routes
// export nextCokeHandler
export default function handler(req, res) {
return nextCokeHandler(req, res, routes)
}
Client:
// define coke client
const { coke } = nextCokeClient<AppRoutes>()
// call API methods
coke.getName({ name: "John" }).then((res) => {
console.log(res)
})

Fastify giving a react prop to a render with next.js

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;

HowTo: Aquire req.ip from express server within _app.js?

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
}
}
}

Endpoint returns 404 when using custom Express server

I have a Next.js app with two pages. My structure looks like the following:
/pages
/index.js
/city.js
I've created a custom server so that if the user goes to anything other than the home page it should render city.js. For example if you go to myapp.com/phl then the url should stay myapp.com/phl but it should render city.js. The same applies if you go to myapp.com/stl.
Here's my custom server:
const express = require('express');
const next = require('next');
const url = require('url');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handler = app.getRequestHandler();
app.prepare()
.then(() => {
const server = express();
server.get('*', (request, response) => {
return handler(request, response);
});
server.get('/:airportCode', (request, response) => {
console.log('hello from the server');
app.render( request, response, '/city', { ...request.query, ...request.params } );
});
server.listen(3000, error => {
if (error) throw error;
console.log('> Ready on http://localhost:3000');
});
})
.catch(exception => {
console.error(exception.stack);
process.exit(1);
});
When I visit the home page it renders that page fine, but when I go to https://myapp.com/phl I get a 404. Am I missing something?
You need to switch up your page handler with the asterisk page handler:
const express = require('express');
const next = require('next');
const url = require('url');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handler = app.getRequestHandler();
app.prepare()
.then(() => {
const server = express();
server.get('/:airportCode', (request, response) => {
console.log('hello from the server');
app.render( request, response, '/city', { ...request.query, ...request.params } );
});
server.get('*', (request, response) => {
return handler(request, response);
});
server.listen(3000, error => {
if (error) throw error;
console.log('> Ready on http://localhost:3000');
});
})
.catch(exception => {
console.error(exception.stack);
process.exit(1);
});
The function of asterisk is like a fallback for any path that isn't handled by the previous function.

Resources