I'm trying to render an XML file when pointing to www.example.com/sitemap.xml. Since the project was developed using Next.js, the routing works with the js files stored in the pages directory:
example.com/help -> help.js
example.com/info -> info.js
So, is there any way to achieve this by avoiding accessing the backend?
JS Version
/pages/sitemap.xml.jsx
import React from 'react'
class Sitemap extends React.Component {
static async getInitialProps({ res }) {
res.setHeader('Content-Type', 'text/xml')
res.write(`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
...
</urlset>`)
res.end()
}
}
export default Sitemap
TS Version
/pages/sitemap.xml.tsx
import { GetServerSideProps } from 'next'
import React from 'react'
const Sitemap: React.FC = () => null
export const getServerSideProps: GetServerSideProps = async ({ res }) => {
if (res) {
res.setHeader('Content-Type', 'text/xml')
res.write(`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
</urlset>`)
res.end()
}
return {
props: {},
}
}
export default Sitemap
API Version
/pages/api/sitemap.xml.tsx
import type { NextApiRequest, NextApiResponse } from 'next'
import { getAllContentPagesQuery, getAllShopProductsQuery } from './utils/requests'
export default async (req: NextApiRequest, res: NextApiResponse<string>) => {
const pages = await getAllContentPagesQuery()
const products = await getAllShopProductsQuery()
const frontUrl = "https://localhost:3000"
const pagesAndSlugs = [...pages, ...products].map(url => ({
url: `${frontUrl}${'Variations' in url ? '/product/' : '/'}${url.slug}`, // -> /page1, /product/myproduct...
updatedAt: url.updatedAt,
}))
const urls = pagesAndSlugs.map(
({ url, updatedAt }) => `
<url>
<loc>${url}</loc>
<lastmod>${new Date(updatedAt).toISOString()}</lastmod>
</url>
`
)
.join('')
res
.setHeader('Content-Type', 'text/xml')
.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
)
.status(200)
.send(`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls}
</urlset>`)
}
:D
Add a static file called sitemap.xml under public directory
public/sitemap.xml
after build you can access www.yourdomain.com/sitemap.xml
Read more on static files: https://nextjs.org/docs/basic-features/static-file-serving
You can use nextjs api routes.
path - pages/api/sitemap.ts
content -
import type { NextApiRequest, NextApiResponse } from 'next';
import { SitemapStream, streamToPromise } from 'sitemap';
async function sitemap(req: NextApiRequest, res: NextApiResponse<string>) {
try {
const smStream = new SitemapStream({
hostname: `https://${req.headers.host}`,
});
// List of posts
const posts: string[] = ['hello'];
// Create each URL row
posts.forEach(() => {
smStream.write({
url: `/post/hello`,
changefreq: 'daily',
priority: 0.9,
});
});
// End sitemap stream
smStream.end();
// XML sitemap string
const sitemapOutput = (await streamToPromise(smStream)).toString();
res.writeHead(200, {
'Content-Type': 'application/xml',
});
// Display output to user
res.end(sitemapOutput);
} catch (e) {
console.log(e);
res.send(JSON.stringify(e));
}
}
export default sitemap;
In robots.txt you can specify
Sitemap: https://[SITE_DOMAIN_HERE]/api/sitemap
Related
I'm trying to initialise a Recoil atom using the Next API but encountering an error.
The default value is set to the function that makes the call to the Next API endpoint, which then retrieves some data from firebase.
When I then try to use the atom in a component using useRecoilState and log its value, I get this error:
error - TypeError [ERR_INVALID_URL]: Invalid URL
at new NodeError (node:internal/errors:371:5)
at onParseError (node:internal/url:552:9)
at new URL (node:internal/url:628:5)
at dispatchHttpRequest (file:///C:/Users/JoelMcMahon/projects/amt/amtAdmin/amt-admin-utility-v2/node_modules/axios/lib/adapters/http.js:169:20)
at new Promise (<anonymous>)
at httpAdapter (file:///C:/Users/JoelMcMahon/projects/amt/amtAdmin/amt-admin-utility-v2/node_modules/axios/lib/adapters/http.js:105:10)
at Axios.dispatchRequest (file:///C:/Users/JoelMcMahon/projects/amt/amtAdmin/amt-admin-utility-v2/node_modules/axios/lib/core/dispatchRequest.js:46:10)
at Axios.request (file:///C:/Users/JoelMcMahon/projects/amt/amtAdmin/amt-admin-utility-v2/node_modules/axios/lib/core/Axios.js:140:33)
at wrap (file:///C:/Users/JoelMcMahon/projects/amt/amtAdmin/amt-admin-utility-v2/node_modules/axios/lib/helpers/bind.js:5:15)
at eval (webpack-internal:///./src/Modules/getUsers.ts:12:58) {
input: '/api/users/getUsersFromDatabase',
code: 'ERR_INVALID_URL',
page: '/'
}
I've also tried setting the default value of the atom as a selector that makes the query using async await but still get the error.
Here are the relevant files:
atoms.js:
import { atom } from "recoil";
import { getUsers } from "../Modules/getUsers";
export const userListPromise = atom({
key: "userListPromise",
default: getUsers(),
});
getUsers.ts:
import axios from "axios";
export const getUsers = (): Promise<any> => {
return new Promise((resolve, reject) => {
axios({
method: "GET",
url: "/api/users/getUsersFromDatabase",
})
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
};
getUsersFromDatabase.ts
import axios from "axios";
import type { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const url = //My Cloud Function URL//;
axios({
method: "GET",
url: url,
})
.then((response) => {
res.status(200).json(response.data);
})
.catch((error) => {
res.status(400).json({ message: `Failed to get users: ${error}` });
});
}
UserDisplay.tsx:
import React from "react";
import { useRecoilState } from "recoil";
import { userListPromise } from "../Atoms/atoms";
import { getUsers } from "../Modules/getUsers";
const UserDisplay = () => {
const [userList] = useRecoilState(userListPromise);
console.log(userList);
return (
<div>
</div>
);
};
export default UserDisplay;
If I comment out the lines using the state in UserDisplay.tsx:
const [userList] = useRecoilState(userListPromise);
console.log(userList);
then start the development server, uncomment them and save causing a live reload, then the error does not occur. However, if I then refresh the page or try to start the server initially with those lines uncommented, I get the error.
Any help or guidance would be greatly appreciated.
I'm using next v12.3.1 and recoil v0.7.6
I'm creating a project that uses NestJs for the backend part and React for the frontend part. I would like to add users but there is an error that prevented it. When I make the request using Postman it works fine, but when I try it through the front, it sends a 431 error. I'm using redux/toolkit for sending requests. I'm using http://localhost:3001/. I tried to add a "start": "nest start -- --max-http-header-size=80000" for backend and frontend, but it didn't help me. Attached a request-response screen:
Frontend request
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
import { CreateUserRequest } from "../dto/create-user-request.dto";
import { User } from "../models/User";
export const usersApi = createApi({
reducerPath: "usersApi",
baseQuery: fetchBaseQuery({
baseUrl: "/users",
}),
endpoints: (build) => ({
createUser: build.mutation<User, CreateUserRequest>({
query: (createUserRequest) => ({
url: "/",
method: "POST",
body: createUserRequest,
}),
}),
}),
});
export const { useCreateUserMutation } = usersApi;
backend main.ts
import { ValidationPipe } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
app.useGlobalPipes(new ValidationPipe())
await app.listen(3000);
}
bootstrap();
users.controller.ts
import { Body, Controller, Post } from '#nestjs/common';
import { CreateUserRequest } from './dto/request/create-user-request.dto';
import { UserResponse } from './dto/response/user-response.dto';
import { UsersService } from './users.service';
#Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) { }
#Post()
async createUser(
#Body() createUserRequest: CreateUserRequest
): Promise<UserResponse> {
return this.usersService.createUser(createUserRequest)
}
}
I'm building out a new marketing site for my company using next.js, and I'm running into an issues with URLS. Essentially, I've built a custom API route to access data from our internal database, using Prisma:
getAllDealers.ts
import Cors from 'cors';
import { prisma } from 'lib/prisma';
import { NextApiResponse, NextApiRequest, NextApiHandler } from 'next';
const cors = Cors({
methods: ['GET', 'HEAD'],
});
function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: any) {
return new Promise((resolve, reject) => {
fn(req, res, (result: any) => {
if (result instanceof Error) {
return reject(result);
}
return resolve(result);
});
});
}
const getDealers: NextApiHandler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
await runMiddleware(req, res, cors);
const dealers = await prisma.crm_dealers.findMany({
where: {
active: {
not: 0,
},
},
});
switch (method) {
case 'GET':
res.status(200).send({ dealers, method: method });
break;
case 'PUT':
res.status(500).json({ message: 'sorry, we only accept GET requests', method: method });
break;
default:
res.setHeader('Allow', ['GET']);
res.status(405).end(`Method ${method} Not Allowed`);
}
};
export default getDealers;
And I've built a route to access individual dealers:
getSingleDealer.ts
import Cors from 'cors';
import { prisma } from 'lib/prisma';
import { NextApiResponse, NextApiRequest, NextApiHandler } from 'next';
const cors = Cors({
methods: ['GET', 'HEAD'],
});
function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: any) {
return new Promise((resolve, reject) => {
fn(req, res, (result: any) => {
if (result instanceof Error) {
return reject(result);
}
return resolve(result);
});
});
}
const getDealerById: NextApiHandler = async (req: NextApiRequest, res: NextApiResponse) => {
await runMiddleware(req, res, cors);
const dealer = await prisma.crm_dealers.findUnique({
where: {
id: Number(req.query.id),
},
});
res.status(200).send({ dealer, method: req.method });
};
export default getDealerById;
I can use my getSingleDealer function in getServerSideProps like so:
export const getServerSideProps = async ({ params }: Params) => {
const { uid } = params;
const { dealer } = await getSingleDealer('api/dealer', uid);
return {
props: { dealer },
};
};
And this works just fine. What I need to do though is prettify my URLS. Right now, the way to access a singular dealer's page is dealers/1 with 1 being whatever the ID of the dealer is. I want to have that URL be a string, like dealers/sacramento-ca (that location is also served up in the API) while still accessing the API on the basis of the id so it's searching for an integer, rather than a string. Is that possible within next?
You'd handle the routing in your client with getServerSideProps similarly to what you are doing now. To do so, you need to configure your dynamic routing file or folder name to match your desired format.
Example folder structures are:
pages > dealers > [dealer].tsx = /dealers/sacramento-ca
pages > dealers > [location] > index.tsx = /dealers/sacramento-ca
export const getServerSideProps = async ({ params }: Params) => {
const { uid } = params;
const { dealer } = await getSingleDealer('api/dealer', uid);
if (!dealer ) {
return { notFound: true }
}
return {
props: {
...dealer,
location: 'sacramento-ca', // object key must match your dynamic [folder or file's name]
},
};
};
All dynamic URL parts must be included as a key in the return.
pages > dealers > [state] > index.tsx [city].tsx = /dealers/ca/sacramento
return {
props: {
...dealer,
city: 'sacramento',
state: 'ca',
},
};
Here is a article detailing what you will need to do. It's important to note that sometimes it's desirable to use a catch all route to simplify searching and deeply nested dynamic routes.
I am building a headless wordpress CMS(and using next.js template) .
I was able to successfully deploy to the vercel servers, I then started to add some content, in this case the Wordpress title field.. and this works great on my local server and display the title properly with no errors.. but when I update my git it kills the vercel deployment and I get this error.. I also get this error if I try to use content or dangerouslySetHTML
to clarify, if I remove this, it deploys fine
{ page.title }
Unhandled error during request: TypeError: Cannot read property 'title' of undefined
--
17:01:15.529 | at Page (/vercel/workpath0/.next/serverless/pages/[slug].js:1634:412)
this is my page code [slug].js
import Head from 'next/head'
import Link from 'next/link'
import Container from '../components/container'
import MoreStories from '../components/more-stories'
import HeroPost from '../components/hero-post'
import Intro from '../components/intro'
import Layout from '../components/layout'
import { getAllPagesWithSlug, getAllPagesBySlug } from '../lib/api'
import { CMS_NAME } from '../lib/constants'
import Header from '../components/header'
export default function Page( {page} ) {
return (
<>
<Layout>
<Head>
<title></title>
</Head>
<Header />
<Container>
{ page.title }
</Container>
</Layout>
</>
)
}
export async function getStaticProps({ params }) {
const data = await getAllPagesBySlug(params.slug)
console.log(data)
return {
props: {
page: data.page,
},
}
}
export async function getStaticPaths() {
const allPages = await getAllPagesWithSlug()
return {
//paths: [ { params: { slug: '${node.uri}' } } ],
paths: allPages.edges.map(({ node }) => `/${node.slug}`) || [],
fallback: true,
}
}
this is my query lib/api
const API_URL = process.env.WORDPRESS_API_URL
async function fetchAPI(query, { variables } = {}) {
const headers = { 'Content-Type': 'application/json' }
if (process.env.WORDPRESS_AUTH_REFRESH_TOKEN) {
headers[
'Authorization'
] = `Bearer ${process.env.WORDPRESS_AUTH_REFRESH_TOKEN}`
}
const res = await fetch(API_URL, {
method: 'POST',
headers,
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
console.error(json.errors)
throw new Error('Failed to fetch API')
}
return json.data
}
export async function getAllPagesBySlug($id) {
const data = await fetchAPI(`
{
page(id: "${$id}", idType: URI) {
uri
slug
content
title
featuredImage {
node {
sourceUrl
}
}
}
}
`)
return data
}
hi everyone ! i am stuck with an error. ı implemented sitemap.xml.js
fıle in page folder. It is working in dev server but don't work in
production. using vercel hosting. Can you help?
import React from "react";
import axios from "axios";
import { API_URL } from "../lib/services"
const sitemapXML = data => {
let projectsXML = "";
let comUrlsXml = "";
data.map(post => {
const projectURL = https://example/${post.cSlug}/${post.pSlug};
projectsXML += `
<url>
<loc>${projectURL}</loc>
<priority>0.50</priority>
</url>`;
});
data.map(post => {
const comUrls = https://example/t/${post.cSlug};
comUrlsXml += `
<url>
<loc>${comUrls}</loc>
<priority>0.50</priority>
</url>`;
});
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${projectsXML}
${comUrlsXml}
</urlset>`;
};
class Sitemap extends React.Component {
}
export async function getServerSideProps({ res }) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const data = await axios
.get(`${API_URL}/post/get-slugs`)
.then(response => response.data);
res.setHeader("Content-Type", "text/xml");
res.write(sitemapXML(data));
res.end();
return { props: { data } }
}
export default Sitemap;
i get error only prodcution