If you use getServerSideProps()
in Next you can render a page by visiting it. E.g. GET http://localhost:3000/mypages/page1
Or if you use getStaticProps()
with getStaticPaths()
, you can use npm run build
to generate the HTML file (e.g. .next/server/pages
directory).
But what if you don't want to start a server. What if you have a particular page/URL in mind that you want to generate but without starting a server and sending an HTTP GET
request to it? This blog post shows a way to do this with a plain Node script.
Here's a solution to programmatically render a page:
#!/usr/bin/env node
import http from "http";
import next from "next";
async function main(uris) {
const nextApp = next({});
const nextHandleRequest = nextApp.getRequestHandler();
await nextApp.prepare();
const htmls = Object.fromEntries(
await Promise.all(
uris.map((uri) => {
try {
// If it's a fully qualified URL, make it its pathname
uri = new URL(uri).pathname;
} catch {}
return renderPage(nextHandleRequest, uri);
})
)
);
console.log(htmls);
}
async function renderPage(handler, url) {
const req = new http.IncomingMessage(null);
const res = new http.ServerResponse(req);
req.method = "GET";
req.url = url;
req.path = url;
req.cookies = {};
req.headers = {};
await handler(req, res);
if (res.statusCode !== 200) {
throw new Error(`${res.statusCode} on rendering ${req.url}`);
}
for (const { data } of res.outputData) {
const [, body] = data.split("\r\n\r\n");
if (body) return [url, body];
}
throw new Error("No output data has a body");
}
main(process.argv.slice(2)).catch((err) => {
console.error(err);
process.exit(1);
});
To demonstrate I created this sample repo: https://github.com/peterbe/programmatically-render-next-page
Note, that you need to run npm run build
first so Next can have all the static assets ready.
In conclusion
The alternative, in automation, would be run something like this:
▶ npm run build && npm run start &
▶ sleep 5 # give the server a chance to start
▶ xh http://localhost:3000/aboutus
HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 06 Sep 2022 12:23:42 GMT
Etag: "m8ff9sdduo1hk"
Keep-Alive: timeout=5
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: Next.js
<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><title>About Us page</title><meta name="description" content="We do things. I hope."/><link rel="icon" href="/favicon.ico"/><meta name="next-head-count" content="5"/><link rel="preload" href="/_next/static/css/ab44ce7add5c3d11.css" as="style"/><link rel="stylesheet" href="/_next/static/css/ab44ce7add5c3d11.css" data-n-g=""/><link rel="preload" href="/_next/static/css/ae0e3e027412e072.css" as="style"/><link rel="stylesheet" href="/_next/static/css/ae0e3e027412e072.css" data-n-p=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="/_next/static/chunks/webpack-7ee66019f7f6d30f.js" defer=""></script><script src="/_next/static/chunks/framework-db825bd0b4ae01ef.js" defer=""></script><script src="/_next/static/chunks/main-3123a443c688934f.js" defer=""></script><script src="/_next/static/chunks/pages/_app-deb173bd80cbaa92.js" defer=""></script><script src="/_next/static/chunks/996-f1475101e84cf548.js" defer=""></script><script src="/_next/static/chunks/pages/aboutus-41b1f037d974ef60.js" defer=""></script><script src="/_next/static/REJUWXI26y-lp9JVmzJB5/_buildManifest.js" defer=""></script><script src="/_next/static/REJUWXI26y-lp9JVmzJB5/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div class="Home_container__bCOhY"><main class="Home_main__nLjiQ"><h1 class="Home_title__T09hD">About Use page</h1><p class="Home_description__41Owk"><a href="/">Go to the <b>Home</b> page</a></p></main><footer class="Home_footer____T7K"><a href="/">Home page</a></footer></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/aboutus","query":{},"buildId":"REJUWXI26y-lp9JVmzJB5","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
There are probably many great ideas that this can be used for. At work we use getServerSideProps()
and we have too many pages to build them all statically. We need a solution like this to do custom analysis of the rendered HTML to check for broken links by analyzing every generated <a href>
tag.