Skip to content

Conversation

@ZL-Asica
Copy link
Member

✨ Features Updates && πŸ› Bug Fix

πŸ“Œ Description

This PR adds three main capabilities and several quality-of-life improvements:

  1. Google Analytics β€” Added Google Analytics to the whole site for analyzing purposes.

  2. RSS feed for Annual Letters β€” A build-time RSS generator produces public/letters-feed.xml and exposes it via a <link rel="alternate" type="application/rss+xml"> tag. This makes it easy for anyone to subscribe to new DTR annual letters in any feed reader.

  3. Durable logging to Cloudflare R2 β€” Production logs are now persisted to monthly NDJSON files in R2 (in addition to console output) to support lightweight, long-horizon analysis without scraping server logs.

Additionally:

  • Centralizes siteUrl and feed filename in lib/consts.
  • Normalizes ISR revalidation cadences across pages and clarifies the env default to 6 hours (Fix the bug that page setting got overwritten).
  • Restructures lib/airtable/* and lib/r2/* for clarity.
  • Improves page metadata (SEO/preview), external link handling, and type definitions.
  • Tunes image conversion runtime (sharp) to be friendlier on small instances.

πŸ” Feature Changes

  • New component or page
    New utility: utils/generate-rss-feed.ts executed at build via projects/[id]/page.tsx#generateStaticParams.
  • API update
    logger.ts now appends structured logs to monthly NDJSON files in R2 via lib/r2/r2-logs.ts.
  • UI/UX improvement
    RSS <link rel="alternate"> in <head>; richer page descriptions; canonical URLs sourced from a single siteUrl.
  • Other: RSS
    Adds rss dependency and a typed build pipeline for feed generation.

πŸ”„ Refactor Changes

  • Code optimization
    Lowered sharp concurrency (3β†’2) and cache sizing for low-mem hosts.
  • Improve maintainability
    Moved Airtable code to lib/airtable/*, R2 code to lib/r2/*, constants to lib/consts.ts, and annual letter data to lib/annual-letters.ts. Added types/*.d.ts.
  • Remove unnecessary code
    Eliminated inline interfaces in the annual letters module (now in types/letters.d.ts).

πŸ›  Bug Fixes

  • Fixed issue: Inconsistent ISR expectations
    Default REVALIDATE_TIME and page-level revalidate values were not aligned. Page-specific revalidation setting is overwritten by the unstable_cache setting. Now the unstable_cache revalidation is also changed to 21600s (6h) and comments/README updated.
  • Improved error handling
    R2 logging failures are swallowed with a clear console error, so logging can never break page rendering.
  • Performance fix
  • Other:

βœ… Checklist

  1. Feature Updates:
  • Code follows project coding style.
  • Tested in a Next.js environment.
  • Relevant documentation is updated.
  1. Bug Fixes:
  • Bug has been reproduced and verified (see steps below).
  • Fix does not introduce new issues.
  1. Code Refactor:
  • No breaking changes introduced.
  • Performance improvement validated (lower resource usage in image conversion).
  • Documentation updated if necessary.
  1. Dependency Updates:
  • Verified functionality after update.
  • Checked for security vulnerabilities using npm audit / pnpm audit.

πŸ“œ Dependency Changes

Dependency Name Old Version New Version Reason
rss β€” ^1.2.2 Build-time generation of the Annual Letters RSS feed
@types/rss β€” ^0.0.32 Type support for rss

πŸ“ Steps to Reproduce (before fix)

A. Revalidation expectations drift

  1. Set REVALIDATE_TIME="3600" in .env and navigate to /people and /projects.
  2. Observe Airtable fetch cadence didn’t match the commented/default expectations across pages.
  3. After this PR: the default is 6h (21600) and pages explicitly align with that value; comments/README reflect the truth.

B. No durable prod logs

  1. With AIRTABLE_LOG=1 in production, only console logs existed; there was no persistent store for monthly analysis.
  2. After this PR: logs are appended to R2 at logs/YYYY/YYYY-MM.ndjson.

πŸ“ Notable File/Path Changes

  • RSS

    • src/utils/generate-rss-feed.ts (new)
    • src/app/layout.tsx (RSS <link> in <head>)
    • src/app/projects/[id]/page.tsx (invokes feed generation at build)
  • Logging / R2

    • src/lib/logger.ts (now appends to R2)
    • src/lib/r2/index.ts, src/lib/r2/r2-gc.ts, src/lib/r2/r2-logs.ts (new module structure)
  • Airtable & constants

    • src/lib/airtable/* (moved from src/lib/*)
    • src/lib/consts.ts (adds siteUrl, feedFileName, default revalidateTime=21600)
  • Metadata & links

    • src/app/layout.tsx, src/app/letters/page.tsx, src/app/people/page.tsx (descriptions, canonical via siteUrl, external links use prefetch={false})
  • Types & images

    • src/types/letters.d.ts, src/types/images.d.ts, src/types/people.d.ts (expanded/cleaned)
    • src/utils/image-convert.ts (types extracted; concurrency tuned)

πŸ’¬ Additional Notes

  • Concurrency caveat for R2 appends: Current implementation uses a simple GET β†’ append β†’ PUT workflow, which is fine at a six-hour cadence. If write frequency grows or concurrency becomes a concern, switch to a per-event object model (e.g., logs/YYYY/MM/dd/ts.json) with periodic compaction or use R2 object multipart uploads with conditional headers.
  • SEO: Centralized siteUrl prevents hard-coded URL drift and keeps canonical/meta consistent across environments.
  • No breaking changes: Routes, data models, and environment variables retain backward compatibility. New env vars are only used if you opt into R2 logging.

- Even set `export const revalidate = <desired_seconds>` to desired seconds in each pages, the revalidation time will be rewritten by `unstable_cache`'s default setting (in previous case is 3600s, which cause the revalition to be 1 hour for every page).
- Now set the default revalidation time to 6 hours (21600s) in `.env` file as `REVALIDATE_TIME`, and use it in `unstable_cache` in `lib/airtable.ts`.
@ZL-Asica ZL-Asica requested a review from Copilot October 21, 2025 19:45
@ZL-Asica ZL-Asica self-assigned this Oct 21, 2025
@ZL-Asica ZL-Asica added bug general issues with the website enhancement general tag for new features to add labels Oct 21, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds Google Analytics tracking, generates an RSS feed for Annual Letters at build time, and implements durable logging to Cloudflare R2 for production environments. Additionally, it fixes ISR revalidation inconsistencies by standardizing the default to 6 hours (21600s) across all pages and cache configurations.

Key Changes:

  • Introduced Google Analytics via @next/third-parties and RSS feed generation using the rss package
  • Centralized site configuration (siteUrl, feedFileName, revalidateTime) in lib/consts.ts
  • Added R2-backed structured logging that appends NDJSON records to monthly log files

Reviewed Changes

Copilot reviewed 16 out of 25 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/utils/image-convert.ts Extracted WebpOptions type and reduced sharp concurrency from 3 to 2 for lower memory footprint
src/utils/generate-rss-feed.ts New utility to generate RSS feed at build time for Annual Letters
src/lib/r2/r2-logs.ts New module to append structured logs to monthly NDJSON files in R2
src/lib/r2/index.ts Updated import path for constants from local to centralized location
src/lib/logger.ts Enhanced logger to persist records to R2 in addition to console output
src/lib/consts.ts Added siteUrl, feedFileName constants and changed default revalidateTime from 3600s to 21600s
src/lib/annual-letters.ts Removed inline type definitions (moved to types/letters.d.ts)
src/lib/airtable/airtable.ts Updated import to use centralized revalidateTime constant
src/components/home/MediaBanner.tsx Updated import path for annualLetters data
src/app/projects/page.tsx Changed revalidate from 14400s to 21600s and updated comment
src/app/projects/[id]/page.tsx Added RSS feed generation call in generateStaticParams
src/app/people/page.tsx Changed revalidate from 14400s to 43200s, added description metadata, updated import paths
src/app/letters/page.tsx Switched from <a> to <Link> with prefetch={false}, added description metadata
src/app/layout.tsx Added RSS feed link tag, Google Analytics component, and centralized URL constants
package.json Added @next/third-parties, rss, and @types/rss dependencies
README.md Updated default REVALIDATE_TIME documentation from 3600s to 21600s

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@github-actions
Copy link

🎨 Lint Check

⚠️ Lint Issues Found - PLEASE FIX THEM!

yarn run v1.22.22
$ eslint . --fix

/home/runner/work/dtr-web/dtr-web/src/app/projects/[id]/page.tsx
   21:9   error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
   21:28  error  Unsafe call of a(n) `error` type typed value                  ts/no-unsafe-call
   22:3   error  Unsafe return of a value of type error                        ts/no-unsafe-return
   22:10  error  Unsafe call of a(n) `error` type typed value                  ts/no-unsafe-call
   22:21  error  Unsafe member access .map on an `error` typed value           ts/no-unsafe-member-access
   22:34  error  Unsafe assignment of an `any` value                           ts/no-unsafe-assignment
   31:9   error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
   31:26  error  Unsafe call of a(n) `error` type typed value                  ts/no-unsafe-call
   39:9   error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
   42:23  error  Unsafe member access .name on an `error` typed value          ts/no-unsafe-member-access
   43:5   error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
   43:26  error  Unsafe member access .description on an `error` typed value   ts/no-unsafe-member-access
   46:7   error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
   46:23  error  Unsafe member access .banner_image on an `error` typed value  ts/no-unsafe-member-access
   49:7   error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
   49:23  error  Unsafe member access .banner_image on an `error` typed value  ts/no-unsafe-member-access
   60:9   error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
   60:26  error  Unsafe call of a(n) `error` type typed value                  ts/no-unsafe-call
   66:9   error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
   71:60  error  Unsafe member access .name on an `error` typed value          ts/no-unsafe-member-access
   74:16  error  Unsafe member access .banner_image on an `error` typed value  ts/no-unsafe-member-access
   76:16  error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
   76:24  error  Unsafe member access .banner_image on an `error` typed value  ts/no-unsafe-member-access
   78:16  error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
   78:24  error  Unsafe member access .name on an `error` typed value          ts/no-unsafe-member-access
   85:20  error  Unsafe member access .description on an `error` typed value   ts/no-unsafe-member-access
   91:10  error  Unsafe call of a(n) `error` type typed value                  ts/no-unsafe-call
   91:18  error  Unsafe member access .images on an `error` typed value        ts/no-unsafe-member-access
   92:21  error  Unsafe assignment of an `any` value                           ts/no-unsafe-assignment
   92:25  error  Unsafe member access .url on an `any` value                   ts/no-unsafe-member-access
   94:20  error  Unsafe assignment of an `any` value                           ts/no-unsafe-assignment
   94:24  error  Unsafe member access .url on an `any` value                   ts/no-unsafe-member-access
   96:31  error  Unsafe member access .name on an `error` typed value          ts/no-unsafe-member-access
   98:14  error  Unsafe call of a(n) `any` typed value                         ts/no-unsafe-call
   98:18  error  Unsafe member access .description on an `any` value           ts/no-unsafe-member-access
  102:46  error  Unsafe member access .description on an `any` value           ts/no-unsafe-member-access
  111:42  error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
  111:50  error  Unsafe member access .publications on an `error` typed value  ts/no-unsafe-member-access
  114:45  error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
  114:53  error  Unsafe member access .demo_video on an `error` typed value    ts/no-unsafe-member-access
  116:47  error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
  116:55  error  Unsafe member access .sprint_video on an `error` typed value  ts/no-unsafe-member-access
  119:29  error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
  119:37  error  Unsafe member access .id on an `error` typed value            ts/no-unsafe-member-access
  119:50  error  Unsafe assignment of an error typed value                     ts/no-unsafe-assignment
  119:58  error  Unsafe member access .members on an `error` typed value       ts/no-unsafe-member-access

/home/runner/work/dtr-web/dtr-web/src/app/projects/page.tsx
  19:9   error  Unsafe assignment of an error typed value            ts/no-unsafe-assignment
  19:22  error  Unsafe call of a(n) `error` type typed value         ts/no-unsafe-call
  43:8   error  Unsafe call of a(n) `error` type typed value         ts/no-unsafe-call
  43:13  error  Unsafe member access .map on an `error` typed value  ts/no-unsafe-member-access
  44:19  error  Unsafe assignment of an `any` value                  ts/no-unsafe-assignment
  44:23  error  Unsafe member access .id on an `any` value           ts/no-unsafe-member-access
  46:60  error  Unsafe member access .name on an `any` value         ts/no-unsafe-member-access
  50:31  error  Computed name [sig.name] resolves to an `any` value  ts/no-unsafe-member-access
  50:35  error  Unsafe member access .name on an `any` value         ts/no-unsafe-member-access
  52:18  error  Unsafe assignment of an `any` value                  ts/no-unsafe-assignment
  52:22  error  Unsafe member access .name on an `any` value         ts/no-unsafe-member-access
  58:20  error  Unsafe member access .description on an `any` value  ts/no-unsafe-member-access
  64:14  error  Unsafe call of a(n) `any` typed value                ts/no-unsafe-call
  64:18  error  Unsafe member access .projects on an `any` value     ts/no-unsafe-member-access
  65:25  error  Unsafe assignment of an `any` value                  ts/no-unsafe-assignment
  65:33  error  Unsafe member access .id on an `any` value           ts/no-unsafe-member-access
  66:47  error  Unsafe member access .id on an `any` value           ts/no-unsafe-member-access
  68:30  error  Unsafe member access .name on an `any` value         ts/no-unsafe-member-access
  74:30  error  Unsafe member access .description on an `any` value  ts/no-unsafe-member-access
  82:33  error  Unsafe assignment of an `any` value                  ts/no-unsafe-assignment
  82:37  error  Unsafe member access .id on an `any` value           ts/no-unsafe-member-access
  82:50  error  Unsafe assignment of an `any` value                  ts/no-unsafe-assignment
  82:54  error  Unsafe member access .members on an `any` value      ts/no-unsafe-member-access

/home/runner/work/dtr-web/dtr-web/src/app/sitemap.ts
   8:9   error  Unsafe assignment of an error typed value            ts/no-unsafe-assignment
   8:26  error  Unsafe call of a(n) `error` type typed value         ts/no-unsafe-call
   9:9   error  Unsafe assignment of an error typed value            ts/no-unsafe-assignment
   9:46  error  Unsafe call of a(n) `error` type typed value         ts/no-unsafe-call
   9:55  error  Unsafe member access .map on an `error` typed value  ts/no-unsafe-member-access
  10:42  error  Unsafe member access .id on an `any` value           ts/no-unsafe-member-access

βœ– 75 problems (75 errors, 0 warnings)

info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

@ZL-Asica ZL-Asica force-pushed the feature/analytics-subscribing branch from d8306eb to c7c2436 Compare October 21, 2025 19:48
@github-actions
Copy link

🎨 Lint Check

βœ… Lint: No linting issues found!

@ZL-Asica ZL-Asica marked this pull request as ready for review October 21, 2025 19:50
@ZL-Asica ZL-Asica merged commit facb9a3 into main Oct 21, 2025
2 checks passed
@ZL-Asica ZL-Asica deleted the feature/analytics-subscribing branch October 21, 2025 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug general issues with the website enhancement general tag for new features to add size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants