Skip to content

Commit 43538a0

Browse files
committed
Phase 1: Data Layer Modernization
This commit introduces build-time static data generation, replacing runtime GitHub API calls for tools data. Key changes: ## New Build System - Added scripts/build-data.ts: Fetches and consolidates tools data from static-analysis and dynamic-analysis repos at build time - Added prebuild hook in package.json to run build-data before next build - Data is stored in data/tools.json, data/tags.json, data/tool-stats.json, and data/tag-stats.json (all gitignored as they're generated) ## New Static Data Utilities - utils/static-data.ts: Core utility for reading pre-built static data - utils/tools.ts: Simplified tools API using static data - utils/tools-with-votes.ts: Merges tools with votes data - utils/firebase-votes.ts: Simplified Firebase votes fetching - utils/tags.ts: Tags utility using static data - utils/filters.ts: Filtering utility (moved from utils-api) - utils/stats.ts: View statistics utility ## Updated Pages - pages/index.tsx: Uses new static data utilities - pages/tool/[slug].tsx: Uses new static data utilities - pages/tag/[slug].tsx: Uses new static data utilities ## Build/Deploy Updates - Dockerfile: Removed manual tools.json download (now handled by build-data) - deploy.yml: Updated to hash generated data files for cache busting ## Benefits - No runtime GitHub API calls for tools data - Faster page generation (data is pre-fetched) - Simplified data flow - Old utils-api code preserved for backwards compatibility Note: API routes and old utils-api code are preserved for now. The /tools page still uses API routes which will be addressed in Phase 2 with InstantSearch integration.
1 parent e3a1bc7 commit 43538a0

17 files changed

Lines changed: 1835 additions & 44 deletions

.github/workflows/deploy.yml

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,29 +87,24 @@ jobs:
8787
app_id: ${{ secrets.GH_APP_ID }}
8888
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
8989

90-
# We want to redeploy, whenever the tools.json file changes
91-
# so make sure to hash the file and use it as a tag for the Docker image
92-
- name: 'Download tools.json File'
93-
run: curl -sL https://github.com/analysis-tools-dev/static-analysis/raw/master/data/api/tools.json -o ./tools.json
94-
95-
- name: 'Generate Hash of tools.json File'
96-
id: tools_json_hash
97-
run: echo "tools_json_hash=$(sha256sum tools.json | cut -c1-7)" >> $GITHUB_ENV
90+
# Hash the generated tools.json from the build (created by npm run build-data)
91+
# to ensure we redeploy when tools data changes
92+
- name: 'Generate Hash of built tools data'
93+
run: |
94+
echo "tools_hash=$(sha256sum data/tools.json | cut -c1-7)" >> $GITHUB_ENV
9895
99-
# Also take screenshots.json into account, which is at
100-
# https://github.com/analysis-tools-dev/assets/blob/master/screenshots.json
96+
# Also take screenshots.json into account for cache busting
10197
- name: 'Download screenshots.json File'
10298
run: curl -sL https://github.com/analysis-tools-dev/assets/raw/master/screenshots.json -o ./screenshots.json
10399

104100
- name: 'Generate Hash of screenshots.json File'
105-
id: screenshots_json_hash
106-
run: echo "screenshots_json_hash=$(sha256sum screenshots.json | cut -c1-7)" >> $GITHUB_ENV
101+
run: echo "screenshots_hash=$(sha256sum screenshots.json | cut -c1-7)" >> $GITHUB_ENV
107102

108-
# Image hash is a combination of the hashes
103+
# Image hash is a combination of commit + data hashes
109104
- name: 'Set IMAGE_NAME hash'
110105
run: |
111106
short_hash=$(echo "${{ github.sha }}" | cut -c1-7)
112-
echo "IMAGE_NAME=${{ env.IMAGE_NAME }}:$short_hash-${{ env.tools_json_hash }}-${{ env.screenshots_json_hash }}" >> $GITHUB_ENV
107+
echo "IMAGE_NAME=${{ env.IMAGE_NAME }}:$short_hash-${{ env.tools_hash }}-${{ env.screenshots_hash }}" >> $GITHUB_ENV
113108
114109
- name: 'Build Docker Image'
115110
env:

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,10 @@ algolia-index.js
4646

4747
credentials.json
4848

49+
# Generated data files (created by npm run build-data)
50+
/data/tools.json
51+
/data/tags.json
52+
/data/tool-stats.json
53+
/data/tag-stats.json
4954

5055
.vscode

Dockerfile

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@ ARG PROJECT_ID
1111

1212
COPY . /src
1313

14-
# Download tools.json directly in Dockerfile and detect changes
15-
# This is done to ensure that we redeploy the app whenever the tools.json changes
16-
ADD https://raw.githubusercontent.com/analysis-tools-dev/static-analysis/master/data/api/tools.json /src/data/api/tools.json
17-
14+
# Build runs npm run build-data (prebuild hook) which fetches tools data
15+
# from GitHub repos and generates static JSON files, then runs next build
1816
RUN npm run build
19-
RUN rm /src/credentials.json /src/data/api/tools.json
17+
RUN rm /src/credentials.json
2018

2119
FROM node:20
2220
WORKDIR /src

MODERNIZATION_TODO.md

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
# Modernization TODO List
2+
3+
This document outlines the steps needed to modernize the analysis-tools.dev website by simplifying the backend and leveraging modern tools: **Algolia + InstantSearch + Headless UI + Static JSON/CMS**.
4+
5+
## Current Architecture Overview
6+
7+
The current setup has several layers of complexity:
8+
- Next.js with Server-Side Rendering (SSR) and API routes
9+
- Firebase/Firestore for votes storage
10+
- GitHub API for fetching tools data from external repos
11+
- File-based caching (`cache-manager-fs-hash`)
12+
- Custom search context and filtering logic
13+
- React Query for client-side data fetching
14+
- Docker deployment on Google Cloud Run via Pulumi
15+
16+
---
17+
18+
## Phase 1: Data Layer Modernization
19+
20+
### 1.1 Static JSON Data Source
21+
- [x] Create a build-time script to fetch and consolidate `tools.json` from both `static-analysis` and `dynamic-analysis` repos
22+
- [x] Store consolidated data in `data/tools.json` at build time
23+
- [x] Remove runtime GitHub API calls (`utils-api/tools.ts``getTools()`) - New utilities in `utils/tools.ts`, `utils/static-data.ts`
24+
- [ ] Remove Octokit dependency (`@octokit/core`) - Keep for now, old code still present
25+
- [ ] Remove file-based cache (`cache-manager`, `cache-manager-fs-hash`) - Keep for now, old code still present
26+
- [ ] Delete `utils-api/cache.ts` - Keep for now, will remove in cleanup phase
27+
28+
### 1.2 Votes Data Migration
29+
- [ ] **Option A**: Migrate votes to Algolia as a field in the tools index
30+
- [ ] **Option B**: Use a lightweight service (e.g., Supabase, PlanetScale) for votes
31+
- [x] **Option C**: Keep Firebase but simplify to client-side only voting - Implemented `utils/firebase-votes.ts`
32+
- [ ] Remove server-side Firebase Admin SDK (`firebase-admin`) - Still needed for votes
33+
- [ ] Delete `firebase-key.json` and related credentials handling - Still needed
34+
- [ ] Remove `utils-api/firebase.ts` - Keep for now, will remove in cleanup phase
35+
- [ ] Remove `utils-api/votes.ts` server-side logic - Keep for now, API routes still use it
36+
- [x] Simplify or remove `utils-api/toolsWithVotes.ts` - New `utils/tools-with-votes.ts` created
37+
38+
### 1.3 Remove API Routes
39+
- [ ] Delete `pages/api/tools.ts` - Keep for now, tools page still uses it
40+
- [ ] Delete `pages/api/paginated-tools.ts` - Keep for now, tools page still uses it
41+
- [ ] Delete `pages/api/tags/*` - Keep for now
42+
- [ ] Delete `pages/api/vote/*` - Keep for now, voting still works via API
43+
- [ ] Delete `pages/api/votes/*` - Keep for now
44+
- [ ] Delete `pages/api/mostViewed.ts` - Keep for now
45+
- [ ] Delete `pages/api/popularLanguages.ts` - Keep for now
46+
- [ ] Delete `pages/api/articles.ts` (if not needed) - Keep for now
47+
- [ ] Remove entire `utils-api/` directory (migrate necessary utils to `utils/`) - Partially done
48+
49+
### 1.4 New Static Data Utilities (Phase 1 Additions)
50+
- [x] Created `scripts/build-data.ts` - Build-time data fetching script
51+
- [x] Created `utils/static-data.ts` - Static data reader utility
52+
- [x] Created `utils/tools.ts` - Simplified tools utility
53+
- [x] Created `utils/tools-with-votes.ts` - Tools with votes merger
54+
- [x] Created `utils/firebase-votes.ts` - Simplified Firebase votes utility
55+
- [x] Created `utils/tags.ts` - Simplified tags utility
56+
- [x] Created `utils/filters.ts` - Filtering utility (moved from utils-api)
57+
- [x] Created `utils/stats.ts` - Stats utility for views data
58+
- [x] Updated `package.json` with `build-data` script and `prebuild` hook
59+
- [x] Updated `.gitignore` to exclude generated data files
60+
- [x] Updated `Dockerfile` to use new build process
61+
- [x] Updated `.github/workflows/deploy.yml` to use generated data for hashing
62+
- [x] Updated `pages/index.tsx` to use new static data utilities
63+
- [x] Updated `pages/tool/[slug].tsx` to use new static data utilities
64+
- [x] Updated `pages/tag/[slug].tsx` to use new static data utilities
65+
66+
---
67+
68+
## Phase 2: Search & Filtering with Algolia + InstantSearch
69+
70+
### 2.1 Algolia Index Enhancement
71+
- [ ] Enhance `algolia-index.ts` to run at build time (not from deployed API)
72+
- [ ] Add all filterable attributes to Algolia index:
73+
- `languages` (facet)
74+
- `categories` (facet)
75+
- `types` (facet)
76+
- `licenses` (facet)
77+
- `pricing`/`plans` (facet)
78+
- `votes` (for sorting)
79+
- `deprecated` (filter)
80+
- [ ] Configure Algolia dashboard:
81+
- Set up facets for filtering
82+
- Configure searchable attributes ranking
83+
- Set up sorting indices (by votes, by name, etc.)
84+
85+
### 2.2 InstantSearch Integration
86+
- [ ] Upgrade `react-instantsearch` to latest version
87+
- [ ] Replace custom `SearchProvider` (`context/SearchProvider.tsx`) with InstantSearch provider
88+
- [ ] Delete `context/SearchProvider.tsx`
89+
- [ ] Replace `utils-api/filters.ts` logic with Algolia facets
90+
- [ ] Create new InstantSearch widgets:
91+
- [ ] `SearchBox` component
92+
- [ ] `RefinementList` for language filters
93+
- [ ] `RefinementList` for category filters
94+
- [ ] `RefinementList` for type filters
95+
- [ ] `RefinementList` for license filters
96+
- [ ] `RefinementList` for pricing filters
97+
- [ ] `SortBy` component for sorting
98+
- [ ] `Hits` component for results
99+
- [ ] `Pagination` or `InfiniteHits` component
100+
101+
### 2.3 Replace Custom Data Fetching
102+
- [ ] Remove React Query (`@tanstack/react-query`) for tools fetching
103+
- [ ] Delete `components/tools/queries/tools.ts`
104+
- [ ] Delete `components/tools/queries/languages.ts`
105+
- [ ] Delete `components/tools/queries/others.ts`
106+
- [ ] Delete `components/tools/queries/index.ts`
107+
- [ ] Keep React Query only if needed for non-search data (blog posts, etc.)
108+
109+
---
110+
111+
## Phase 3: UI Modernization with Headless UI
112+
113+
### 3.1 Replace Custom UI Components
114+
- [ ] Install `@headlessui/react`
115+
- [ ] Replace custom dropdown (`components/elements/Dropdown`) with Headless UI `Listbox`
116+
- [ ] Replace mobile filters drawer with Headless UI `Dialog`
117+
- [ ] Create accessible filter components using Headless UI:
118+
- [ ] `Disclosure` for collapsible filter sections
119+
- [ ] `Combobox` for searchable language selector
120+
- [ ] `Switch` for toggle filters (e.g., "Show deprecated")
121+
- [ ] `Popover` for filter tooltips/info
122+
123+
### 3.2 Component Cleanup
124+
- [ ] Audit and simplify `components/elements/`
125+
- [ ] Remove unused components
126+
- [ ] Standardize component patterns with Headless UI
127+
128+
---
129+
130+
## Phase 4: Page Architecture Simplification
131+
132+
### 4.1 Convert SSR Pages to Static (SSG)
133+
- [ ] Convert `pages/tools/index.tsx` from `getServerSideProps` to `getStaticProps`
134+
- [ ] All filtering/search handled client-side via InstantSearch
135+
- [ ] Keep `pages/tool/[slug].tsx` as static (`getStaticProps` + `getStaticPaths`) ✓ (already done)
136+
- [ ] Keep `pages/tag/[slug].tsx` as static ✓ (already done)
137+
- [ ] Consider Incremental Static Regeneration (ISR) for data freshness
138+
139+
### 4.2 Simplify Page Components
140+
- [ ] Refactor `ListPageComponent.tsx` to use InstantSearch
141+
- [ ] Remove infinite scroll complexity (use Algolia pagination)
142+
- [ ] Remove `useRouterPush` hook for search state (InstantSearch handles URL)
143+
- [ ] Delete `hooks/` directory if no longer needed
144+
145+
### 4.3 Data Flow Cleanup
146+
- [ ] Remove `utils/query.ts` (objectToQueryString)
147+
- [ ] Remove `utils/urls.ts` if only used for API URLs
148+
- [ ] Simplify `utils/constants.ts`
149+
150+
---
151+
152+
## Phase 5: Build & Deployment Simplification
153+
154+
### 5.1 Build Process
155+
- [ ] Create `scripts/build-data.ts`:
156+
- Fetch tools from GitHub repos
157+
- Fetch star history (optional, can be removed)
158+
- Merge and validate data
159+
- Output to `data/tools.json`
160+
- Update Algolia index
161+
- [ ] Update `package.json` scripts:
162+
```json
163+
{
164+
"prebuild": "npm run build-data",
165+
"build-data": "ts-node scripts/build-data.ts",
166+
"build": "next build"
167+
}
168+
```
169+
- [ ] Remove runtime data fetching from build process
170+
171+
### 5.2 Environment Variables Cleanup
172+
- [ ] Remove `GOOGLE_APPLICATION_CREDENTIALS`
173+
- [ ] Remove `GH_TOKEN` (if data is fetched at build time from public repos)
174+
- [ ] Keep only:
175+
- `ALGOLIA_APP_ID`
176+
- `ALGOLIA_API_KEY` (search-only key for client)
177+
- `ALGOLIA_ADMIN_KEY` (for indexing, build-time only)
178+
- `PUBLIC_HOST`
179+
180+
### 5.3 Docker & Deployment
181+
- [ ] Simplify `Dockerfile` (no credentials needed at runtime)
182+
- [ ] Consider static export (`next export`) if no server features needed
183+
- [ ] Evaluate moving from Cloud Run to static hosting (Vercel, Netlify, Cloudflare Pages)
184+
- [ ] Simplify or remove Pulumi infrastructure if using static hosting
185+
- [ ] Update `.github/workflows/deploy.yml`
186+
187+
---
188+
189+
## Phase 6: CMS Integration (Optional)
190+
191+
### 6.1 Content Management
192+
- [ ] Evaluate CMS options for non-tool content:
193+
- Blog posts (currently markdown in `data/blog/`)
194+
- FAQ content (`data/faq.json`)
195+
- Sponsors (`data/sponsors.json`)
196+
- Homepage content (`data/homepage.json`)
197+
- [ ] Consider headless CMS options:
198+
- Contentlayer (for markdown)
199+
- Sanity
200+
- Strapi
201+
- Directus
202+
- [ ] Or keep as static JSON/Markdown if editorial workflow is simple
203+
204+
---
205+
206+
## Phase 7: Dependency Cleanup
207+
208+
### 7.1 Remove Unused Dependencies
209+
```json
210+
{
211+
"remove": [
212+
"@octokit/core",
213+
"cache-manager",
214+
"cache-manager-fs-hash",
215+
"firebase-admin",
216+
"algoliasearch-helper",
217+
"@tanstack/react-query"
218+
],
219+
"keep": [
220+
"algoliasearch",
221+
"react-instantsearch",
222+
"next",
223+
"react",
224+
"react-dom"
225+
],
226+
"add": [
227+
"@headlessui/react"
228+
]
229+
}
230+
```
231+
232+
### 7.2 DevDependencies Cleanup
233+
- [ ] Remove unused type definitions
234+
- [ ] Update ESLint config for simplified codebase
235+
- [ ] Consider switching to Biome or similar for faster linting
236+
237+
---
238+
239+
## Phase 8: Code Quality & Maintenance
240+
241+
### 8.1 TypeScript Improvements
242+
- [ ] Remove all `@ts-nocheck` and `@ts-ignore` comments
243+
- [ ] Fix type errors in `context/SearchProvider.tsx` (before deletion)
244+
- [ ] Ensure strict TypeScript throughout
245+
246+
### 8.2 Testing
247+
- [ ] Add unit tests for data transformation utilities
248+
- [ ] Add integration tests for search functionality
249+
- [ ] Add E2E tests for critical user flows
250+
251+
### 8.3 Documentation
252+
- [ ] Update `README.md` with new architecture
253+
- [ ] Document new build process
254+
- [ ] Document Algolia configuration
255+
- [ ] Create contributing guide for the simplified codebase
256+
257+
---
258+
259+
## Migration Checklist Summary
260+
261+
### Files to Delete
262+
- [ ] `utils-api/` (entire directory)
263+
- [ ] `pages/api/` (entire directory)
264+
- [ ] `context/SearchProvider.tsx`
265+
- [ ] `components/tools/queries/` (entire directory)
266+
- [ ] `firebase-key.json`
267+
- [ ] `algolia-index.js` (keep only `.ts` version)
268+
269+
### Files to Create
270+
- [ ] `scripts/build-data.ts`
271+
- [ ] `components/search/SearchProvider.tsx` (InstantSearch wrapper)
272+
- [ ] `components/search/SearchBox.tsx`
273+
- [ ] `components/search/Filters.tsx`
274+
- [ ] `components/search/Results.tsx`
275+
- [ ] `components/ui/` (Headless UI wrappers)
276+
277+
### Files to Significantly Modify
278+
- [ ] `pages/tools/index.tsx`
279+
- [ ] `pages/index.tsx`
280+
- [ ] `components/tools/listPage/ListPageComponent/ListPageComponent.tsx`
281+
- [ ] `next.config.js`
282+
- [ ] `package.json`
283+
- [ ] `.github/workflows/deploy.yml`
284+
- [ ] `Dockerfile`
285+
286+
---
287+
288+
## Benefits After Modernization
289+
290+
1. **Simpler Architecture**: No server-side API routes, no runtime data fetching
291+
2. **Better Performance**: Static pages with client-side search (instant)
292+
3. **Lower Costs**: Static hosting is cheaper than Cloud Run
293+
4. **Better DX**: Less code to maintain, clearer data flow
294+
5. **Better Search UX**: Algolia InstantSearch provides superior search experience
295+
6. **Accessibility**: Headless UI components are accessible by default
296+
7. **Scalability**: Algolia handles search at any scale
297+
8. **Reliability**: Fewer moving parts = fewer things to break

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"private": true,
44
"scripts": {
55
"dev": "next dev",
6+
"build-data": "ts-node -r tsconfig-paths/register --compiler-options '{\"module\":\"CommonJS\"}' ./scripts/build-data.ts",
7+
"prebuild": "npm run build-data",
68
"build": "next build",
79
"search-index": "ts-node -r tsconfig-paths/register --compiler-options '{\"module\":\"CommonJS\"}' ./algolia-index.ts",
810
"start": "next start",

pages/index.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,21 @@ import homepageData from '@appdata/homepage.json';
1414
import { ArticlePreview, Faq, SponsorData } from 'utils/types';
1515
import { Tool, ToolsByLanguage } from '@components/tools';
1616
import { getArticlesPreviews } from 'utils-api/blog';
17-
import { getPopularLanguageStats } from 'utils-api/popularLanguageStats';
18-
import { getMostViewedTools } from 'utils-api/mostViewedTools';
1917
import { getSponsors } from 'utils-api/sponsors';
2018
import { getFaq } from 'utils-api/faq';
19+
// New static data utilities (Phase 1)
20+
import { getPopularLanguageStats, getMostViewedTools } from 'utils/stats';
21+
import { fetchVotes } from 'utils/firebase-votes';
2122

2223
export const getStaticProps: GetStaticProps = async () => {
2324
const sponsors = getSponsors();
2425
const faq = getFaq();
2526
const previews = await getArticlesPreviews();
26-
const popularLanguages = await getPopularLanguageStats();
27-
const mostViewed = await getMostViewedTools();
27+
28+
// Fetch votes from Firebase and use with static data utilities
29+
const votes = await fetchVotes();
30+
const popularLanguages = getPopularLanguageStats(votes);
31+
const mostViewed = getMostViewedTools(votes);
2832

2933
return {
3034
props: {

0 commit comments

Comments
 (0)