From 3c1f1f40e0312361947f361ba6ce168c1da47686 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 4 May 2026 22:07:50 +0000
Subject: [PATCH 1/7] fix: Substack essays always fetch via public API, add
iframe fallback + improved essay cards
- Remove API key gate: public Substack /api/v1/posts works without auth,
so skip the substackApiKey check that was blocking all CI fetches
- Pass substackHandle through to component for use in fallback
- Iframe embed fallback: when API returns no essays (e.g. blocked fetch),
render the Substack embed widget instead of an empty list
- Essay cards: cover image thumbnail, inline Substack source badge,
flex layout so image+text sit side by side
- Add netlify.toml so Netlify deploy previews trigger on PRs
https://claude.ai/code/session_01P9Bn6gkWtM15yEzGf79XCQ
---
netlify.toml | 14 ++++
pages/index.tsx | 167 +++++++++++++++++++++++++++++-------------------
2 files changed, 117 insertions(+), 64 deletions(-)
create mode 100644 netlify.toml
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..6d5bc60
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,14 @@
+[build]
+ command = "npm run build && npm run export"
+ publish = "out"
+
+[build.environment]
+ NODE_VERSION = "22"
+ NODE_ENV = "production"
+
+# Deploy previews for all PRs
+[context.deploy-preview]
+ command = "npm run build && npm run export"
+
+[context.branch-deploy]
+ command = "npm run build && npm run export"
diff --git a/pages/index.tsx b/pages/index.tsx
index cfad325..59c4d9b 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -22,8 +22,6 @@ import {
layoutDefaultClasses,
StyleClasses,
} from "components/styles";
-import { log } from "lib/log";
-
const classes: StyleClasses = {
...globalClasses,
...layoutDefaultClasses,
@@ -36,18 +34,16 @@ export const getStaticProps = async () => {
const channels = await resolveArenaChannels();
const siteMap = await getSiteMap();
- // Fetch Substack essays - use environment variables for Substack handle and API key
+ // Fetch Substack essays - public API works without a key; key is optional enhancement
const substackHandle = process.env.SUBSTACK_HANDLE || "suruleredotdev";
const substackApiKey = process.env.SUBSTACK_API_KEY;
let substackEssays: SubstackEssay[] = [];
- if (substackHandle && substackApiKey) {
+ if (substackHandle) {
substackEssays = await getSubstackEssays(
substackHandle,
10,
substackApiKey
);
- } else {
- log("ERROR", "Missing Substack handle or API key")
}
const props = {
@@ -55,6 +51,7 @@ export const getStaticProps = async () => {
channels,
siteMap,
substackEssays,
+ substackHandle,
};
return { props, revalidate: 3600 }; // Revalidate every hour for fresh Substack content
@@ -82,6 +79,7 @@ const textVersion = 0;
interface HomePageContentProps extends types.PageProps {
substackEssays?: SubstackEssay[];
+ substackHandle?: string;
}
export const HomePageContent: React.FC = ({
@@ -91,6 +89,7 @@ export const HomePageContent: React.FC = ({
channels,
siteMap,
substackEssays = [],
+ substackHandle = "suruleredotdev",
}) => {
// TODO: render from root page block
const posts = getSitePosts({
@@ -116,74 +115,111 @@ export const HomePageContent: React.FC = ({
ESSAYS
- {/* {JSON.stringify(posts.slice(0, 3), null, 2)} */}
-
- {/* Combine and sort local posts with Substack essays by publication date */}
- {[
+ {(() => {
+ const allItems = [
...posts
?.filter((post) => post.public == true)
- .map((post) => ({ ...post, isExternal: false })),
+ .map((post) => ({ ...post, isExternal: false, image: undefined as string | undefined })),
...substackEssays.map((essay) => ({
...essay,
- title: essay.title,
- description: essay.description,
- published: essay.published,
isExternal: true,
id: `substack-${essay.id}`,
})),
- ]
- ?.sort((a, b) => b.published - a.published)
- .map((item: any, i) => (
-
-
- {item.title}
- {item.isExternal && }
-
+ ].sort((a, b) => b.published - a.published);
-
- —{" "}
- {new Date(item.published).toLocaleDateString("en-US", {
- year: "numeric",
- month: "long",
- day: "numeric",
- })}
-
+ if (allItems.length === 0) {
+ // Fallback: Substack embed iframe when no essay data is available
+ return (
+
+
+
+ );
+ }
-
+ return (
+
+ {allItems.map((item: any, i) => {
+ const href = item.isExternal ? item.url : "/" + idToPagePath[item.id];
+ const dateStr = new Date(item.published).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ });
-
- {item.description?.length > 200
- ? item.description?.substring(0, 197) + "..."
- : item.description}
-
- {item.tags && !item.isExternal ? (
- <>
- {/* TODO: implement tags
-
- {item.tags?.map((tag, i) => (
-
- {tag}
-
- ))} */}
- >
- ) : (
- <>>
- )}
-
- ))}
-
+ return (
+
+
+ {item.image && (
+
+ )}
+
+
+ {item.title}
+ {item.isExternal && }
+
+
+ — {dateStr}
+
+ {item.isExternal && (
+
+ Substack
+
+ )}
+
+
+ {item.description?.length > 200
+ ? item.description?.substring(0, 197) + "..."
+ : item.description}
+
+
+
+
+ );
+ })}
+
+ );
+ })()}
TOOLS
@@ -278,6 +314,7 @@ interface IndexPageProps {
channels: any;
siteMap: any;
substackEssays?: SubstackEssay[];
+ substackHandle?: string;
}
const IndexPage: React.FC = (props) => {
@@ -289,6 +326,7 @@ const IndexPage: React.FC = (props) => {
channels: arenaChannels,
siteMap,
substackEssays,
+ substackHandle,
} = props;
const { isDarkMode } = useDarkMode();
@@ -326,6 +364,7 @@ const IndexPage: React.FC = (props) => {
rootPageBlock={block}
channels={arenaChannels}
substackEssays={substackEssays}
+ substackHandle={substackHandle}
>
From 47a4b464c17d230af87cedf44a3fe7673944542f Mon Sep 17 00:00:00 2001
From: Korede Aderele
Date: Mon, 4 May 2026 22:20:26 -0700
Subject: [PATCH 2/7] Delete netlify.toml
Seems to be breaking the deploys
---
netlify.toml | 14 --------------
1 file changed, 14 deletions(-)
delete mode 100644 netlify.toml
diff --git a/netlify.toml b/netlify.toml
deleted file mode 100644
index 6d5bc60..0000000
--- a/netlify.toml
+++ /dev/null
@@ -1,14 +0,0 @@
-[build]
- command = "npm run build && npm run export"
- publish = "out"
-
-[build.environment]
- NODE_VERSION = "22"
- NODE_ENV = "production"
-
-# Deploy previews for all PRs
-[context.deploy-preview]
- command = "npm run build && npm run export"
-
-[context.branch-deploy]
- command = "npm run build && npm run export"
From 2fefb2787be419fe57da2ae368f3bbf75b81c076 Mon Sep 17 00:00:00 2001
From: Claude
Date: Tue, 5 May 2026 05:30:31 +0000
Subject: [PATCH 3/7] fix: show Substack iframe alongside local posts when
essays are missing
Iframe now renders as a supplement below local posts when substackEssays
is empty, rather than replacing the whole list. Local posts always show.
https://claude.ai/code/session_01P9Bn6gkWtM15yEzGf79XCQ
---
pages/index.tsx | 155 ++++++++++++++++++++++++------------------------
1 file changed, 77 insertions(+), 78 deletions(-)
diff --git a/pages/index.tsx b/pages/index.tsx
index 59c4d9b..fa9611b 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -127,10 +127,80 @@ export const HomePageContent: React.FC = ({
})),
].sort((a, b) => b.published - a.published);
- if (allItems.length === 0) {
- // Fallback: Substack embed iframe when no essay data is available
- return (
-
+ return (
+ <>
+ {allItems.length > 0 && (
+
+ )}
+ {substackEssays.length === 0 && (
- );
- }
-
- return (
-
+ )}
+ >
);
})()}
From 13693ff30844a69465a2ed33aa0d6239b06785ec Mon Sep 17 00:00:00 2001
From: Claude
Date: Tue, 5 May 2026 05:31:53 +0000
Subject: [PATCH 4/7] ci: switch to gh-pages branch deploy + add PR preview
workflow
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- deploy.yml: replace actions/deploy-pages (GitHub Actions source) with
peaceiris/actions-gh-pages so production lands on the gh-pages branch.
Required for PR preview subdirs to coexist with the production root.
- pr-preview.yml: new workflow using rossjrw/pr-preview-action. Builds
the site with NEXT_PUBLIC_BASE_PATH=/pr-preview/pr-{N} so Next.js
basePath/assetPrefix resolve correctly inside the subdir. Posts the
preview URL as a PR comment automatically; cleans up on PR close.
NOTE: requires one settings change in the GitHub repo →
Settings > Pages > Source → "Deploy from a branch" → gh-pages / root
https://claude.ai/code/session_01P9Bn6gkWtM15yEzGf79XCQ
---
.github/workflows/deploy.yml | 34 +++++-----------------
.github/workflows/pr-preview.yml | 50 ++++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+), 27 deletions(-)
create mode 100644 .github/workflows/pr-preview.yml
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 7e8d3e4..95aade7 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -13,19 +13,15 @@ on:
type: string
permissions:
- contents: read
- actions: write
- id-token: write
- pages: write # to deploy to Pages
+ contents: write
jobs:
- build:
+ deploy:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.9.0]
- # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
env:
NOTION_SECRET: ${{ secrets.NOTION_SECRET }}
@@ -50,25 +46,9 @@ jobs:
- run: npm install --include=dev
- run: DEBUG=1 VERBOSE=1 npm run build
- run: npm run export
- - name: Upload Artifacts
- uses: actions/upload-pages-artifact@v3
+ - name: Deploy to gh-pages branch
+ uses: peaceiris/actions-gh-pages@v4
with:
- path: ./out
-
- # Deploy job
- deploy:
- # Add a dependency to the build job
- needs: build
-
- # Deploy to the github-pages environment
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
-
- # Specify runner + deployment step
- runs-on: ubuntu-latest
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
-
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./out
+ cname: surulere.dev
diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml
new file mode 100644
index 0000000..66f2e64
--- /dev/null
+++ b/.github/workflows/pr-preview.yml
@@ -0,0 +1,50 @@
+name: PR Preview
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, closed]
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ preview:
+ runs-on: ubuntu-latest
+ env:
+ NOTION_SECRET: ${{ secrets.NOTION_SECRET }}
+ ARENA_UID: ${{ secrets.ARENA_UID }}
+ ARENA_SECRET: ${{ secrets.ARENA_SECRET }}
+ ARENA_PERSONAL_ACCESS_TOKEN: ${{ secrets.ARENA_PERSONAL_ACCESS_TOKEN }}
+ SUBSTACK_API_KEY: ${{ secrets.SUBSTACK_API_KEY }}
+ SUBSTACK_HANDLE: ${{ vars.SUBSTACK_HANDLE }}
+ NODE_ENV: production
+ LOG_LEVEL: error
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Use Node.js 22.9.0
+ if: github.event.action != 'closed'
+ uses: actions/setup-node@v2
+ with:
+ node-version: '22.9.0'
+
+ - name: Install dependencies
+ if: github.event.action != 'closed'
+ run: npm install --include=dev
+
+ - name: Build
+ if: github.event.action != 'closed'
+ run: npm run build && npm run export
+ env:
+ # Set base path to the PR preview subdir so assets resolve correctly
+ NEXT_PUBLIC_BASE_PATH: /pr-preview/pr-${{ github.event.pull_request.number }}
+
+ - name: Deploy / remove preview
+ uses: rossjrw/pr-preview-action@v1
+ with:
+ source-dir: ./out
+ preview-branch: gh-pages
+ umbrella-dir: pr-preview
+ action: auto
From f5ee6f8d400df018f3f47a079436ffefa05685c0 Mon Sep 17 00:00:00 2001
From: Claude
Date: Tue, 5 May 2026 05:53:38 +0000
Subject: [PATCH 5/7] fix: prevent build crash when ARENA_PERSONAL_ACCESS_TOKEN
or NEXT_PUBLIC_BASE_PATH are unset
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Both getEnv() calls lacked a default value, causing an immediate throw at
module load time during next build — before getStaticProps even runs.
- resolve-arena-channels.ts: ARENA_PERSONAL_ACCESS_TOKEN defaults to ""
so the module loads; empty token skips auth (API call fails gracefully
inside the existing try/catch that returns [])
- _document.tsx: NEXT_PUBLIC_BASE_PATH defaults to ""; base href tag is
only rendered when the value is actually set, avoiding a pointless
as well as the throw
https://claude.ai/code/session_01P9Bn6gkWtM15yEzGf79XCQ
---
lib/resolve-arena-channels.ts | 2 +-
pages/_document.tsx | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/resolve-arena-channels.ts b/lib/resolve-arena-channels.ts
index 6cf72c1..f5d1991 100644
--- a/lib/resolve-arena-channels.ts
+++ b/lib/resolve-arena-channels.ts
@@ -8,7 +8,7 @@ import { log } from "./log";
const ARENA_USER = {
slug: "korede-aderele",
id: 60392,
- token: getEnv("ARENA_PERSONAL_ACCESS_TOKEN"),
+ token: getEnv("ARENA_PERSONAL_ACCESS_TOKEN", ""),
};
export const all_channels: Record = {};
diff --git a/pages/_document.tsx b/pages/_document.tsx
index fd3642b..842b7ba 100644
--- a/pages/_document.tsx
+++ b/pages/_document.tsx
@@ -10,8 +10,8 @@ export default class MyDocument extends Document {
- {getEnv("NODE_ENV") === "production" ? (
-
+ {getEnv("NODE_ENV") === "production" && getEnv("NEXT_PUBLIC_BASE_PATH", "") ? (
+
) : (
<>>
)}
From 2aee28fd745d75908decf3834ba73a9a8fe7f023 Mon Sep 17 00:00:00 2001
From: Claude
Date: Tue, 5 May 2026 05:54:21 +0000
Subject: [PATCH 6/7] chore: commit build artifacts from local next build run
tsconfig.json reformatted by Next.js (no semantic change); package.json
has a few ^ ranges resolved to exact versions by npm during install.
https://claude.ai/code/session_01P9Bn6gkWtM15yEzGf79XCQ
---
package-lock.json | 20 ++++++++++----------
package.json | 6 +++---
tsconfig.json | 21 +++++++++++++++++----
3 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index eb70f3f..ef5e113 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -54,9 +54,9 @@
},
"devDependencies": {
"@next/bundle-analyzer": "^12.3.4",
- "@types/node": "^17.0.23",
+ "@types/node": "17.0.45",
"@types/node-fetch": "^3.0.3",
- "@types/react": "^18.0.15",
+ "@types/react": "18.3.28",
"@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
@@ -70,7 +70,7 @@
"npm-run-all": "^4.1.5",
"parcel": "2.6.x",
"prettier": "^2.7.1",
- "typescript": "^4.9.5",
+ "typescript": "4.9.5",
"yarn": "1.22.x"
},
"engines": {
@@ -2568,14 +2568,14 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "18.3.23",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
- "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
- "csstype": "^3.0.2"
+ "csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
@@ -4408,9 +4408,9 @@
}
},
"node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/csv2geojson": {
diff --git a/package.json b/package.json
index a04a6bb..014021b 100644
--- a/package.json
+++ b/package.json
@@ -86,9 +86,9 @@
},
"devDependencies": {
"@next/bundle-analyzer": "^12.3.4",
- "@types/node": "^17.0.23",
+ "@types/node": "17.0.45",
"@types/node-fetch": "^3.0.3",
- "@types/react": "^18.0.15",
+ "@types/react": "18.3.28",
"@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
@@ -102,7 +102,7 @@
"npm-run-all": "^4.1.5",
"parcel": "2.6.x",
"prettier": "^2.7.1",
- "typescript": "^4.9.5",
+ "typescript": "4.9.5",
"yarn": "1.22.x"
},
"resolutions": {
diff --git a/tsconfig.json b/tsconfig.json
index 73d6662..3471573 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "es2016",
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@@ -15,9 +19,18 @@
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
- "typeRoots": ["./node_modules/@types"],
+ "typeRoots": [
+ "./node_modules/@types"
+ ],
"incremental": true
},
- "exclude": ["node_modules"],
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "site.config.ts"]
+ "exclude": [
+ "node_modules"
+ ],
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ "site.config.ts"
+ ]
}
From c44f108b222d40ebf15956479c2d3ff04c2677af Mon Sep 17 00:00:00 2001
From: Claude
Date: Thu, 14 May 2026 01:20:32 +0000
Subject: [PATCH 7/7] revert: restore original deploy.yml (GitHub Pages
artifact flow)
Reverts the peaceiris/actions-gh-pages change back to the original
actions/upload-pages-artifact + actions/deploy-pages two-job setup.
PR preview via rossjrw/pr-preview-action requires this to change but
will be revisited separately.
https://claude.ai/code/session_01P9Bn6gkWtM15yEzGf79XCQ
---
.github/workflows/deploy.yml | 33 ++++++++++++++++++++++++++-------
1 file changed, 26 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 95aade7..5376e85 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -13,15 +13,19 @@ on:
type: string
permissions:
- contents: write
+ contents: read
+ actions: write
+ id-token: write
+ pages: write # to deploy to Pages
jobs:
- deploy:
+ build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.9.0]
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
env:
NOTION_SECRET: ${{ secrets.NOTION_SECRET }}
@@ -46,9 +50,24 @@ jobs:
- run: npm install --include=dev
- run: DEBUG=1 VERBOSE=1 npm run build
- run: npm run export
- - name: Deploy to gh-pages branch
- uses: peaceiris/actions-gh-pages@v4
+ - name: Upload Artifacts
+ uses: actions/upload-pages-artifact@v3
with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./out
- cname: surulere.dev
+ path: ./out
+
+ # Deploy job
+ deploy:
+ # Add a dependency to the build job
+ needs: build
+
+ # Deploy to the github-pages environment
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+
+ # Specify runner + deployment step
+ runs-on: ubuntu-latest
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4