summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornavewindre <boneyaard@gmail.com>2026-02-11 02:05:39 +0100
committernavewindre <boneyaard@gmail.com>2026-02-11 02:05:39 +0100
commitd9d55620a6f4f44b2c5069da107b2b3b111390ed (patch)
treefbee83ccabeee18a04a691d61b60f68e98fa6553
parent3032a8495174f4f583f52c4e9429b6d4d357dc0c (diff)
seo renderer
-rw-r--r--.gitignore3
-rw-r--r--web/renderer/index.js94
-rw-r--r--web/renderer/package.json15
-rwxr-xr-xweb/renderer/run.sh4
-rw-r--r--web/renderer/server.sh3
-rw-r--r--web/src/blog.tsx18
-rw-r--r--web/src/index-page.tsx3
-rw-r--r--web/src/index.html7
-rw-r--r--web/src/jsx.tsx20
-rw-r--r--web/static/main.css79
-rwxr-xr-xweb/update-remote.sh11
11 files changed, 229 insertions, 28 deletions
diff --git a/.gitignore b/.gitignore
index e52f7e4..fd1eb13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,6 @@
web/dist
web/node_modules
web/package-lock.json
+web/renderer/cache
+web/renderer/node_modules
+web/renderer/package-lock.json
diff --git a/web/renderer/index.js b/web/renderer/index.js
new file mode 100644
index 0000000..03caa1c
--- /dev/null
+++ b/web/renderer/index.js
@@ -0,0 +1,94 @@
+const http = require( 'http' );
+const fs = require( 'fs' );
+const path = require( 'path' );
+const crypto = require( 'crypto' );
+const puppeteer = require( 'puppeteer' );
+const express = require( 'express' );
+
+const PORT = 6860;
+const CACHE_DIR = path.join( __dirname, 'cache' );
+
+const host = "https://networkheaven.net";
+const app = express();
+
+if( !fs.existsSync( CACHE_DIR ) ) fs.mkdirSync( CACHE_DIR );
+
+function hash( str ) {
+ return crypto.createHash( 'sha1' ).update( str ).digest( 'hex' );
+}
+
+let browser = null;
+let cache = async ( req, res ) => {
+ const { url } = req;
+ console.log( `incoming req from ${req.headers['user-agent']} for ${url}` );
+
+ const urlObj = new URL( req.url, host );
+ const target = urlObj.href;
+
+ const key = hash( target );
+ const file = path.join( CACHE_DIR, key + '.html' );
+
+ if( fs.existsSync( file ) ) {
+ const html = fs.readFileSync( file );
+ res.writeHead( 200, { 'content-type': 'text/html' } );
+ return res.end( html );
+ }
+
+ try {
+ const page = await browser.newPage();
+
+ const r = await page.goto( target, {
+ waitUntil: 'networkidle2',
+ timeout: 30000
+ } );
+
+ const html = await page.content();
+ console.log( "caching page: " + target );
+ fs.writeFileSync( file, html );
+
+ await page.close();
+ const headers = r.headers();
+
+ res.writeHead( 200, {
+ 'content-type': headers['content-type'],
+ 'date': headers['date'],
+ 'etag': headers['etag'],
+ } );
+ res.end( html );
+
+ } catch( e ) {
+ res.writeHead( 500, { 'content-type': 'text/plain' } );
+ res.end( 'render failed: ' + e.message );
+ }
+}
+
+app.get( "/", cache );
+app.get( /\/$/, cache );
+app.get( /#$/, cache );
+app.get( /^\/[^.]*$/, cache );
+app.use( async ( req, res ) => {
+ let url = new URL( req.url, host );
+ const r = await fetch( url.href );
+
+ if( r.ok ) {
+ const html = await r.text();
+ const headers = [];
+ for( const [k, v] of r.headers.entries() )
+ headers[k] = v;
+
+ res.writeHead( 200, {
+ 'content-type': headers['content-type'],
+ 'date': headers['date'],
+ 'etag': headers['etag'],
+ } );
+ res.end( html );
+ }
+} );
+
+(async () => {
+ browser = await puppeteer.launch( { headless: 'new' } );
+ app.listen( PORT, async () => {
+
+ console.log( "listening on http://localhost:" + PORT );
+ } );
+})();
diff --git a/web/renderer/package.json b/web/renderer/package.json
new file mode 100644
index 0000000..1b92952
--- /dev/null
+++ b/web/renderer/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "renderer",
+ "version": "1.0.0",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "description": "",
+ "dependencies": {
+ "express": "^5.2.1",
+ "puppeteer": "^24.37.2"
+ }
+}
diff --git a/web/renderer/run.sh b/web/renderer/run.sh
new file mode 100755
index 0000000..b67ca91
--- /dev/null
+++ b/web/renderer/run.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+npm i
+screen -m -d node index.js
diff --git a/web/renderer/server.sh b/web/renderer/server.sh
new file mode 100644
index 0000000..293c5fb
--- /dev/null
+++ b/web/renderer/server.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+node index.js
diff --git a/web/src/blog.tsx b/web/src/blog.tsx
index bfef434..1fcfec2 100644
--- a/web/src/blog.tsx
+++ b/web/src/blog.tsx
@@ -8,12 +8,9 @@ let reqErr = 0;
let hljs = null;
let hljs_imported = 0;
-async function importHlJs() {
- if( hljs ) return;
- if( hljs_imported ) return;
- hljs_imported = 1;
- hljs = ( await import( 'highlight.js' ) ).default;
+async function highlightCode() {
+ if( !hljs || !hljs_imported ) return setTimeout( highlightCode, 500 );
const elements = $( "code" );
elements.each( ( _, e ) => {
@@ -21,6 +18,14 @@ async function importHlJs() {
} );
}
+async function importHlJs() {
+ if( hljs ) return;
+ if( hljs_imported ) return;
+
+ hljs_imported = 1;
+ hljs = ( await import( 'highlight.js' ) ).default;
+}
+
function urlForHref( href: string, isdir: boolean ) {
const url = new URL( window.location.href );
let path = url.pathname;
@@ -105,19 +110,20 @@ async function populatePost() {
$( "title" ).html( title );
}
- importHlJs();
$( "#package-entries" ).find( ".spinner" ).remove();
$( "#package-entries" ).css( "display", "none" );
$( "#blog-entry" ).html( text );
$( "#blog-entry" ).css( "display", "flex" );
$( "#back-btn" ).css( "display", "block" );
+ setTimeout( highlightCode );
} catch( e ) {
return populateEntries();
}
}
export default function Blog() {
+ importHlJs();
entries = [];
reqErr = 0;
setTimeout( async () => {
diff --git a/web/src/index-page.tsx b/web/src/index-page.tsx
index a754ef6..71a2fdc 100644
--- a/web/src/index-page.tsx
+++ b/web/src/index-page.tsx
@@ -3,6 +3,8 @@ import Home from "./home";
import Blog from "./blog";
import Pkgs from "./pkg";
+JSX.setDefaultTitle( "networkheaven.net" );
+
JSX.addRoute( "/", () => <Home /> );
JSX.addRoute( "/blog", () => <Blog /> );
JSX.addRoute( "/blog/*", () => <Blog /> );
@@ -10,6 +12,7 @@ JSX.addRoute( "/pkg", () => <Pkgs /> );
JSX.addRoute( "/pkg/*", () => <Pkgs /> );
window.onpopstate = JSX.onPopState;
+document.head.appendChild( <link rel="shortcut icon" href="/static/nh.ico" /> );
const url = new URL( window.location.href );
JSX.navigateParams( url.pathname, url.searchParams.entries() );
diff --git a/web/src/index.html b/web/src/index.html
index b14f5b5..68eba57 100644
--- a/web/src/index.html
+++ b/web/src/index.html
@@ -3,11 +3,14 @@
<head>
<meta charset="UTF-8">
<meta name="description" content="hi i'm aura and this is my website about stuff.">
- <meta name="viewport" content="width=device-width,user-scalable=yes">
+ <meta name="viewport" content="user-scalable=yes">
<title>networkheaven.net</title>
<link rel="stylesheet" href="/static/main.css">
<link href="/static/highlight.css" rel="preload" as="style" onload="this.rel='stylesheet'">
- <link rel="shortcut icon" href="/static/nh.ico" type="image">
+ <link rel="icon" href="data:image/png;base64,iVBORw0KGgo=">
+ <noscript>
+ <link rel="shortcut icon" href="/static/nh.ico" type="image">
+ </noscript>
</head>
<body>
<div id="moneyjsx-root">
diff --git a/web/src/jsx.tsx b/web/src/jsx.tsx
index a92fc11..749e93f 100644
--- a/web/src/jsx.tsx
+++ b/web/src/jsx.tsx
@@ -11,8 +11,9 @@ export interface Route {
const routes: Route[] = [];
let err404page = "/";
let rootId = "moneyjsx-root";
-let onprenavigate: Function = () => {};
-let onpostnavigate: Function = () => {};
+let defaultTitle = "";
+export let onprenavigate: Function = () => {};
+export let onpostnavigate: Function = () => {};
function routeForPath( route: string ) : Function | null {
if( !routes[route] ) {
@@ -37,6 +38,10 @@ export function setRootId( rootId: string ) {
rootId = rootId;
}
+export function setDefaultTitle( title: string ) {
+ defaultTitle = title;
+}
+
/**
* adds a route component to the routes list
* the component function must return either a jquery or a DOM element
@@ -87,6 +92,7 @@ export function navigate( route: string ) {
window.history.pushState( {}, null, url.href );
onprenavigate();
+ setTitle();
const el = $( cb() );
curRoute = cb;
@@ -111,6 +117,7 @@ export function navigateParams( route: string, params: any ) {
window.history.pushState( {}, null, url.href );
onprenavigate();
+ setTitle();
const el = $( cb() );
curRoute = cb;
@@ -143,6 +150,7 @@ export function navigateParamsSilent( route: string, params: any ) {
return navigateSilent( err404page );
onprenavigate();
+ setTitle();
const el = $( cb() );
curRoute = cb;
@@ -162,6 +170,7 @@ export function navigateSilent( route: string ) {
return navigateSilent( err404page );
onprenavigate();
+ setTitle();
const el = $( cb() );
curRoute = cb;
@@ -185,6 +194,7 @@ export function onPopState() {
return;
onprenavigate();
+ setTitle();
const el = $( cb() );
curRoute = cb;
@@ -251,7 +261,7 @@ export function createElement( tag: any, props: any, ...children: any ) {
const lowercaseName = name.toLowerCase();
if( lowercaseName in window ) {
- element.addEventListener( lowercaseName.substring(2 ), value );
+ element.addEventListener( lowercaseName.substring( 2 ), value );
continue;
}
}
@@ -288,3 +298,7 @@ export function createElement( tag: any, props: any, ...children: any ) {
export function createFragment( props: any ) {
return props.children;
}
+
+function setTitle() {
+ document.title = defaultTitle;
+}
diff --git a/web/static/main.css b/web/static/main.css
index 79f28f4..dd00386 100644
--- a/web/static/main.css
+++ b/web/static/main.css
@@ -44,6 +44,7 @@
margin: 0;
font-weight: normal;
}
+
input[type="radio"] {
appearance: none;
background-color: var( --back );
@@ -95,8 +96,8 @@ input[type="radio"]:checked::before {
}
code {
- font-family: Code;
- font-size: 13px;
+ font-family: Terminal;
+ font-size: 18px;
white-space: pre-wrap;
background: #212325;
padding: 5px 7px !important;
@@ -354,7 +355,7 @@ h5 {
}
#blog-entry ul {
- padding-left: 20px;
+ padding-left: 30px;
}
@media( max-width: 1100px ) {
@@ -375,7 +376,6 @@ h5 {
}
.page-title {
- height: 35px;
}
@@ -402,8 +402,8 @@ h5 {
}
#sidebar h4 {
- font-family: JPN12 !important;
- font-size: 13px !important;
+ font-family: JPN12;
+ font-size: 13px;
}
#package-entries table {
@@ -429,7 +429,7 @@ h5 {
}
#sidebar h4 {
- font-size: 10px !important;
+ font-size: 10px;
margin-bottom: 0px;
margin-top: 0px;
}
@@ -447,18 +447,15 @@ h5 {
margin-top: 7px;
}
- .page-title {
- height: 30px;
- }
-
#sidebar h3 {
- font-size: 20px !important;
+ font-size: 20px;
}
.sidebar-row {
margin-bottom: 10px;
}
+
#package-entries table {
font-family: JPN12 !important;
font-size: 8px !important;
@@ -477,9 +474,9 @@ h5 {
}
-@media( max-width: 460px ) {
+@media( max-width: 500px ) {
#sidebar {
- width: 170px !important;
+ width: 150px;
margin-left: 5px;
margin-right: 5px;
}
@@ -492,18 +489,32 @@ h5 {
padding-top: 10px;
}
+ #blog-entry ul {
+ padding-left: 20px;
+ }
+
+ #blog-entry h4 {
+ font-size: 13px;
+ }
+
+ .page-title h3 {
+ font-size: 17px;
+ margin-top: 8px;
+ margin-bottom: 0px;
+ }
+
#ascii-art {
font-size: 4px;
}
#sidebar h4 {
- font-size: 8px !important;
+ font-size: 8px;
margin-bottom: 0px;
margin-top: 0px;
}
#sidebar h3 {
- font-size: 17px !important;
+ font-size: 17px;
}
h5 {
@@ -512,12 +523,46 @@ h5 {
font-weight: normal;
margin-top: 4px;
}
+
+ #blog-entry {
+ font-size: 10px;
+ }
+
+ #blog-entry code {
+ font-size: 10px;
+ }
}
@media( max-width: 350px ) {
- #ascii-art {
+ #ascii-art {
font-size: 3px;
}
+
+ #sidebar h4 {
+ font-size: 6px;
+ margin-bottom: 0px;
+ margin-top: 0px;
+ }
+
+ #sidebar h3 {
+ font-size: 14px;
+ }
+
+ #blog-entry {
+ font-size: 7px;
+ }
+
+ #blog-entry code {
+ font-size: 7px;
+ }
+
+ .sidebar-row {
+ margin-bottom: 13px;
+ }
+
+ #sidebar {
+ width: 90px;
+ }
}
diff --git a/web/update-remote.sh b/web/update-remote.sh
new file mode 100755
index 0000000..a861b60
--- /dev/null
+++ b/web/update-remote.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+REPLY=0
+read -p "are you sure you want to update the remote website? (y/n) " REPLY
+
+if [[ "$REPLY" == "y" ]]; then
+ for i in $(ls -1 ./dist/); do
+ echo "copying $i"
+ scp -r ./dist/$i root@networkheaven.net:/var/www/html/
+ done
+fi