I have a basic fullstack app built with Node, Koa, and Preact. I'm trying to get Preact routing to work. I created a component and route for an About
page. When I start the Preact dev server I can access the /about
page successfully at localhost:5173/about
.
The problem is that when I start my Node server I get Not Found
when I try to go to localhost:8080/about
. However, I can successfully render the homepage by going to /
.
(root) index.js
const server = require('./server');
const port = 8080;
server.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
server\index.js
const Koa = require('koa');
const serve = require('koa-static');
const path = require('path');
const server = new Koa();
// Serve static files
server.use(serve(path.join(__dirname, '../client/dist')));
module.exports = server;
client\src\pages\home\index.jsx
export function Home() {
return (
<h1>Preact App Homepage</h1>
);
}
client\src\pages\about\index.jsx
export function About() {
return (
<h1>About Page</h1>
);
}
client\src\index.jsx
import { LocationProvider, Router, Route, hydrate, prerender as ssr } from 'preact-iso';
import { Header } from './components/Header.jsx';
import { Home } from './pages/Home/index.jsx';
import { About } from './pages/_about.jsx';
import { NotFound } from './pages/_404.jsx';
import './style.css';
export function App() {
return (
<LocationProvider>
<Header />
<main>
<Router>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route default component={NotFound} />
</Router>
</main>
</LocationProvider>
);
}
if (typeof window !== 'undefined') {
hydrate(<App />, document.getElementById('app'));
}
export async function prerender(data) {
return await ssr(<App {...data} />);
}
client\dist\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" >
<link rel="icon" type="image/svg+xml" href="/vite.svg" >
<meta name="viewport" content="width=device-width, initial-scale=1.0" >
<meta name="color-scheme" content="light dark" >
<title>Vite + Preact</title>
<script type="module" crossorigin src="/assets/index-7GmgnfZn.js"></script>
<link rel="modulepreload" crossorigin href="/assets/index-BgxwHnNi.js">
<link rel="stylesheet" crossorigin href="/assets/index-C_LgOj56.css">
</head>
<body>
<div id="app"><header><nav><a href="/" class="active">Home</a><a href="/404">404</a></nav></header><main><h1>Preact App Homepage</h1></main><script type="isodata"></script></div>
</body>
</html>
It looks like you might have two separate issues:
/
The Koa issue is pretty simple: it looks for HTML files in your static output that match the provided URL, and when it cannot find anything, it falls back to a 404. However, this is essentially only a problem as you're missing HTML content. You may want to configure Koa to instead route all traffic to say /404
when HTML cannot be found, or perhaps /
, but it's very subjective. Koa provides it's own Not Found
response by default which you may want to think about overriding, but it's not the main problem here.
As for prerendering, the Preact prerenderer in @preact/preset-vite
(full disclosure: I wrote it) will crawl your app looking for links to prerender. Essentially, it'll render /
, read the resulting HTML, and extract links within (<a href="/about">About Us</a>
-> /about
) to continue to prerender with, stopping once all pages have been prerendered and no more links have been found. This however can be an issue if you haven't actually linked to a route you want prerendered; typically you'd see this with error pages more than anything, as you're probably not going to link to (say) /404
or /500
, but you might want them prerendered all the same.
In the default prerender template we add links to the header (which you haven't uploaded here) to cover this case, but I'd guess you've removed them, hence why the /about
page isn't prerendered.
To fix this, you have two options:
additionalPrerenderRoutes
plugin option to explicitly add pages// vite.config.js
export default defineConfig({
plugins: [
preact({
prerender: {
enabled: true,
// ...
additionalPrerenderRoutes: ['/about', '/error-page', '...']
}
})
]
});