Skip to content

Commit 373064b

Browse files
committed
Implement SSR
1 parent ce1f25d commit 373064b

11 files changed

Lines changed: 121 additions & 58 deletions

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SERVER_PORT=3000

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@
2525
<title>LBRY Web</title>
2626
</head>
2727
<body>
28-
<div id="root"></div>
28+
<div id="root"><!--root--></div>
2929
</body>
3030
</html>

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
8-
"build": "vite build",
8+
"build": "vite build && vite build --ssr server.ts",
99
"preview": "vite preview",
1010
"test": "eslint",
11-
"start": "node server.ts"
11+
"start": "node dist-ssr/server.js"
1212
},
1313
"dependencies": {
1414
"dotenv": "^17.2.3",

server.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ import type Express from "@types/express";
22
import type { NextFunction } from "@types/express";
33
import { configDotenv } from "dotenv";
44
import express from "express";
5+
import fs from "fs";
56
import http, { Server } from "http";
67
import path from "path";
8+
import process from "process";
9+
import React, { ReactNode } from "react";
10+
import { renderToString } from "react-dom/server";
11+
import App from "~/App";
712

813
const app: Express.Application = express();
914

1015
configDotenv({ override: true, quiet: true });
1116

1217
app.use(express.json());
13-
app.use(express.static("dist"));
18+
app.use("/assets", express.static("dist/assets"));
19+
app.use("/favicon.ico", express.static("dist/favicon.ico"));
1420

1521
app.get(
1622
"/api/proxy",
@@ -81,10 +87,38 @@ app.post(
8187
},
8288
);
8389

84-
app.get("*fallback", (_, res: Express.Response): void => {
85-
res.sendFile(path.join(path.resolve(), "dist/index.html"));
90+
//app.use(vite.middlewares);
91+
92+
app.use("*all", (req: Express.Request, res: Express.Response): void => {
93+
const url: string = req.originalUrl;
94+
95+
const app: ReactNode = React.createElement(App, { url: url });
96+
97+
const indexPath: string = path.join(path.resolve(), "dist/index.html");
98+
99+
let isNotFound: boolean = false;
100+
101+
let rendered: string;
102+
try {
103+
rendered = renderToString(app);
104+
} catch (e) {
105+
if (e.message === "404_NOT_FOUND") {
106+
isNotFound = true;
107+
rendered = "<span>404 - Not found</span>";
108+
}
109+
}
110+
111+
const template: string = fs.readFileSync(indexPath, "utf-8");
112+
113+
const html: string = template.replace("<!--root-->", rendered);
114+
115+
res.status(isNotFound ? 404 : 200).send(html);
86116
});
87117

88-
const server: Server = http.createServer(app).listen(3000);
118+
const port: number = parseInt(process.env.SERVER_PORT) || 3000;
119+
120+
const server: Server = http.createServer(app).listen(port, (): void => {
121+
console.info(`Server is running in port ${port}.`);
122+
});
89123

90124
export default server;

src/App.tsx

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,23 @@
11
import React, { JSX, StrictMode, useState } from "react";
2-
import { Route, Routes } from "react-router";
2+
import Props from "react";
33
import AppRouter from "~/AppRouter";
4-
import ClaimPage from "~/ClaimPage";
5-
import Discover from "~/Discover";
6-
import Following from "~/Following";
7-
import Home from "~/Home";
8-
import Library from "~/Library";
9-
import Lists from "~/Lists";
10-
import NotFound from "~/NotFound";
11-
import SearchPage from "~/SearchPage";
12-
import SettingsPage from "~/SettingsPage";
13-
import Tags from "~/Tags";
14-
import WalletPage from "~/WalletPage";
4+
import AppRoutes from "~/AppRoutes";
155
import Aside from "~/components/Aside";
166
import Header from "~/components/Header";
177

188
// import Footer from "~/components/Footer";
199

20-
function App(): JSX.Element {
10+
function App({ url }: Props & { url?: string }): JSX.Element {
2111
const [isMenuOpen, setMenuOpen] = useState(false);
2212
const [isMenuShown] = useState(true);
2313

2414
return (
2515
<StrictMode>
26-
<AppRouter>
16+
<AppRouter url={url}>
2717
<Header menuOpen={isMenuOpen} menuOpenSetter={setMenuOpen} />
2818
{isMenuShown ? <Aside open={isMenuOpen} /> : null}
2919
<main>
30-
<Routes>
31-
<Route index path="/" element={<Home />} />
32-
<Route index path="/following" element={<Following />} />
33-
<Route index path="/tags" element={<Tags />} />
34-
<Route index path="/discover" element={<Discover />} />
35-
<Route index path="/library" element={<Library />} />
36-
<Route index path="/lists" element={<Lists />} />
37-
38-
<Route path="/claim/*" element={<ClaimPage />} />
39-
<Route path="/wallet" element={<WalletPage />} />
40-
<Route path="/search" element={<SearchPage />} />
41-
<Route path="/settings" element={<SettingsPage />} />
42-
43-
<Route path="*" element={<NotFound />} />
44-
</Routes>
20+
<AppRoutes />
4521
</main>
4622
{/*<Footer/>*/}
4723
</AppRouter>

src/AppHistory.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,21 @@ function useAppHistory(): object {
1919
if (navigationType === NavigationType.Push) {
2020
stack.current.splice(index + 1);
2121
stack.current.push(location);
22-
idx.current = history.state.idx;
22+
idx.current = history.state?.idx;
2323
setIndex(index + 1); // eslint-disable-line
2424
}
2525
if (navigationType === NavigationType.Pop) {
26-
if (idx.current > history.state.idx) {
26+
if (idx.current > history.state?.idx) {
2727
setIndex(index - 1);
2828
}
29-
if (idx.current < history.state.idx) {
29+
if (idx.current < history.state?.idx) {
3030
setIndex(index + 1);
3131
}
32-
idx.current = history.state.idx;
32+
idx.current = history.state?.idx;
3333
}
3434
if (navigationType === NavigationType.Replace) {
3535
stack.current[index] = location;
36-
idx.current = history.state.idx;
36+
idx.current = history.state?.idx;
3737
}
3838
}, [location, navigationType]); // eslint-disable-line
3939

src/AppRouter.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
import { JSX, PropsWithChildren } from "react";
2-
import { BrowserRouter, MemoryRouter } from "react-router";
1+
import Props, { JSX, PropsWithChildren } from "react";
2+
import { BrowserRouter, MemoryRouter, StaticRouter } from "react-router";
33

44
const isElectron: boolean = false; // TODO Add detection
55

6-
function AppRouter({ children }: PropsWithChildren): JSX.Element {
6+
function AppRouter({
7+
url,
8+
children,
9+
}: PropsWithChildren & { url?: string }): JSX.Element {
710
if (isElectron) {
811
return <MemoryRouter>{children}</MemoryRouter>;
912
}
13+
if (import.meta.env.SSR) {
14+
return <StaticRouter location={url}>{children}</StaticRouter>;
15+
}
1016
return <BrowserRouter>{children}</BrowserRouter>;
1117
}
1218

src/AppRoutes.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React, { JSX } from "react";
2+
import { Route, Routes } from "react-router";
3+
import ClaimPage from "~/ClaimPage";
4+
import Discover from "~/Discover";
5+
import Following from "~/Following";
6+
import Home from "~/Home";
7+
import Library from "~/Library";
8+
import Lists from "~/Lists";
9+
import NotFound from "~/NotFound";
10+
import SearchPage from "~/SearchPage";
11+
import SettingsPage from "~/SettingsPage";
12+
import Tags from "~/Tags";
13+
import WalletPage from "~/WalletPage";
14+
15+
function AppRoutes(): JSX.Element {
16+
return (
17+
<Routes>
18+
<Route index path="/" element={<Home />} />
19+
<Route index path="/following" element={<Following />} />
20+
<Route index path="/tags" element={<Tags />} />
21+
<Route index path="/discover" element={<Discover />} />
22+
<Route index path="/library" element={<Library />} />
23+
<Route index path="/lists" element={<Lists />} />
24+
25+
<Route path="/claim/*" element={<ClaimPage />} />
26+
<Route path="/wallet" element={<WalletPage />} />
27+
<Route path="/search" element={<SearchPage />} />
28+
<Route path="/settings" element={<SettingsPage />} />
29+
30+
<Route path="*" element={<NotFound />} />
31+
</Routes>
32+
);
33+
}
34+
35+
export default AppRoutes;

src/NotFound.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
import { StaticRouter } from "react-router";
2+
13
function NotFound() {
4+
if (import.meta.env.SSR) {
5+
throw new Error("404_NOT_FOUND");
6+
}
7+
28
return <div style={{ fontSize: "100px", textAlign: "center" }}>404</div>;
39
}
410

src/WalletPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function WalletPage() {
1818
import.meta.env.VITE_DAEMON_PROXY === "true",
1919
).then((json) => {
2020
if (json.error) {
21-
document.getElementById("wallet").innerHTML = json.error.message;
21+
//document.getElementById("wallet").innerHTML = json.error.message;
2222
return;
2323
}
2424
setWallet(json.result);
@@ -34,7 +34,7 @@ function WalletPage() {
3434
import.meta.env.VITE_DAEMON_PROXY === "true",
3535
).then((json) => {
3636
if (json.error) {
37-
document.getElementById("transactions").innerHTML = json.error.message;
37+
//document.getElementById("transactions").innerHTML = json.error.message;
3838
return;
3939
}
4040
setTransactions(json.result?.items || []);

0 commit comments

Comments
 (0)