Building Test Static School Website: ... Using ThemeWagon Si-Education Next.js Template - Part 2

Last updated on 8 Feb. 2026
This post is a continuation of my recent post: Building Test Static School Website: Next.js static export vs Plain React, Using ThemeWagon Si-Education Next.js Template - Part 1.

Summary

Simple school website – testschoolws – Current Architecture

    Decap CMS Admin UI   https://eloquent-sunburst-0d336b.netlify.app/admin

            ↓ Admin edits and publishes content

    Standalone Data Site (GitHub Pages)   https://ravisiyer.github.io/testschoolws-data
            /data/data.json

            ↓ Client-side fetch at runtime

    Live Education Website (Static Site on GitHub Pages)  https://ravisiyer.github.io/testschoolwspub

Main Features

  • Static HTML/CSS/JS exported by Next.js (output: export)

  • Client components fetch data/data.json

  • Text labels, menus, courses, mentors, testimonials are data-driven

  • Content updates require no rebuild of the website

  • Admin users can update content via CMS UI

    • Requires GitHub login with write access to the associated public GitHub data repository: https://github.com/ravisiyer/testschoolws-data.
    • After admin edit, school website will show updated data if browser data is hard-refreshed (Ctrl + F5 on Chrome).

Original si-education template testschoolws is based on

Using Decap CMS as admin UI to edit data.json

This needed creation of a standalone GitHub data repo and using a Netlify site for GitHub authentication - Details on how it was setup.

Environment variable controls which site data loads from

NEXT_PUBLIC_SCHOOL_DATA_BASE_URL env variable was introduced in the schoolws project (testschoolws repo). If it is defined, then the school website dynamically (via Next.js Client Components) gets its data from data.json on that site. If it is not defined, the website gets the data (dynamically again) from data.json in the website itself.

Website loads fast

The website loads quite fast even on my mobile phone using 4G Internet. Why it feels fast even on 4G:
  • HTML shell loads immediately

  • JS bundle is cached aggressively

  • JSON payload is tiny

  • Browser caching + HTTP/2 does the rest

SEO disadvantages for UI Dynamic load from JSON 

Because content is fetched after page load:

  • Search engines see empty or minimal HTML initially

  • Google will execute JS eventually

  • But:

    • Indexing is slower

    • Other crawlers may not execute JS at all

This is the biggest functional downside.

Mitigations:

  • Pre-render critical content

  • Hybrid approach (some static, some dynamic)

  • Move fetch to Server Components / build-time fetch

But client-side content is SEO-weaker.

Hybrid approach (some static/baked-in, some dynamic) is risky for production site

Use case: mentor deletion by admin on standalone data site but website has old data with same mentor existing.
  1. User loads page

  2. Page renders with baked-in mentor A

  3. Client fetch completes

  4. Mentor A disappears (because admin deleted it)

  5. Cards reflow, layout jumps, content count changes

From a human trust perspective, this is disastrous:

  • “Did I imagine that mentor?”

  • “Is the site broken?”

  • “Is this page unstable?”

  • “Are they manipulating content?”

Users do not reason about hydration, skeletons, or CMS flows. They reason emotionally.

A site that visibly changes facts after load feels untrustworthy.

UI inconsistency between baked-in data and live standalone data is a major production risk.

That alone is sufficient reason to reject the hybrid “baked-in + client refresh” approach for a serious public site.

The deeper architectural issue 

You are mixing two sources of truth:

  1. Build-time truth (baked JSON)

  2. Runtime truth (standalone data repo)

Any system with dual truth will eventually lie to someone.

You cannot guarantee: order consistency, presence consistency, semantic consistency, timing consistency

And once admins start actively using the CMS (which they will, because it’s easy), divergence becomes normal, not exceptional.


Strong recommendation: choose ONE of these models

✅ Model A — Fully static, SEO-first (recommended for education sites)

  • Data is part of the build

  • Admin edits trigger a rebuild (automated)

  • Next.js SSG / ISR / Pages rebuild

  • Single source of truth

Pros

  • Perfect SEO

  • Stable UI

  • Predictable behavior

  • User trust preserved

Cons

  • Slight delay between admin edit and live site (minutes)

  • Requires automation (but that is solvable)

This is what universities, course platforms, and documentation sites do.


✅ Model B — Fully client-driven, app-like (acceptable for dashboards)

  • No baked data at all

  • Empty or skeleton UI until fetch completes

  • Treat site as an app, not a document

Pros

  • Instant admin updates

  • Simple mental model

  • No inconsistency

Cons

  • SEO is weak

  • First paint depends on JS

  • Not ideal for public education marketing pages

This is fine for admin panels, logged-in portals, SaaS dashboards.

========================================================
========================================================

Details

Related ChatGPT (CG) chat: si-education adaptation

Towards the end of Part 1 post, a short section Trimmed site to minimum needed for a simple mentor site covers the initial adaptation of si-education template. This post covers further adaptation work.

Me: The si-education template site is a landing page UI shell for a simple education site offering various courses handled by various mentors. It does not have details pages for any course or mentor. This has to be added on to the app. The app does have a documentation page which is a separate route - src\app\(site)\documentation\page.tsx - from the app home page route. Other than sign in/ sign out pages which I have commented out, this documentation page is the only other page in the app besides the home page.

This documentation page is documentation of the template code and so is not needed in a live school site. It can be duplicated and modified to hold parameterized course and mentor pages. To start with, the data for these pages for various courses and mentors can be picked up from data.json - html fragments as a property value. I think this would be a useful upgrade to the simple static education site.

CG: Your assessment is accurate, and your proposed direction is both technically sound and proportionate to the current maturity of the site.

Let me structure this into (A) what the template gives you, (B) why the documentation route is the right starting point, and (C) a concrete, low-risk way to implement course and mentor detail pages without over-engineering.


A. What the si-education template actually is

As you correctly observed:

  • It is a single-page landing UI shell

  • Intended to showcase:

    • categories

    • courses (in summary)

    • mentors (in summary)

  • But no detail routes exist

  • The only non-home route is:

src/app/(site)/documentation/page.tsx

Which is:

  • a static content page

  • already styled

  • already wired into the App Router

  • but semantically irrelevant for a real school site

So functionally, the app is:

/            → landing page
/documentation → template author documentation (throwaway)

This is typical of ThemeForest / ThemeWagon templates.


B. Why reusing the documentation route is a good idea

Your instinct here is exactly right.

Why this is the correct move

  1. It already exists as a separate route

    • No router wiring needed

    • No layout duplication needed

    • No auth entanglement

  2. It already renders rich, scrollable content

    • Headings

    • Sections

    • Long-form text

    • Code / prose blocks (which you can simplify)

  3. It proves the layout works for non-landing pages

    • Header

    • Footer

    • Spacing

    • Typography

  4. Zero risk to the home page

    • You can evolve it independently

In other words, this page is a perfect sacrificial prototype for content-heavy pages.


C. A clean, incremental upgrade path

I would recommend the following three-step evolution, keeping things deliberately simple.


Step 1: Duplicate documentation → parameterized detail page

Instead of hard-replacing documentation, do this:

src/app/(site)/courses/[slug]/page.tsx
src/app/(site)/mentors/[slug]/page.tsx

Then copy the documentation page into each and simplify it.

This immediately gives you URLs like:

/courses/react-basics
/mentors/john-doe

No CMS, no database, no backend.


Step 2: Use data.json as the content source (as you suggested)

Your idea of embedding HTML fragments in data.json is reasonable at this stage, especially because:

  • You are not building a public SaaS

  • Content volume is low

  • Author is technical (you)

  • Git-based editing is acceptable

Example:

{
  "courses": [
    {
      "slug": "react-basics",
      "title": "React Basics",
      "contentHtml": "<p>This course covers...</p>"
    }
  ],
  "mentors": [
    {
      "slug": "ravi-s-iyer",
      "name": "Ravi S. Iyer",
      "bioHtml": "<p>30+ years in software...</p>"
    }
  ]
}

Then in [slug]/page.tsx:

  • Load data.json

  • Find the matching entry

  • Render dangerouslySetInnerHTML

This is perfectly acceptable for a controlled site like this.


Step 3: Link summaries → detail pages

From the landing page sections:

  • Courses → link to /courses/[slug]

  • Mentors → link to /mentors/[slug]

This instantly upgrades the site from:

“marketing landing page”

to:

“actual informational education site”

without changing the overall design language.
...


Me: Thanks for validating the next step plan. ... Some jargon related questions:
What exactly is a landing page?
In this si-education template case, the home page and landing page seem to be same. Am I right?
Is the / route always viewed as home page for a Next.js app?

CG: These are good questions, and they are worth answering precisely, because “landing page”, “home page”, and “/ route” are often used loosely and end up meaning different things in different contexts.

I will answer them in increasing technical specificity.


1. What exactly is a landing page?

A landing page is a page whose primary purpose is conversion, not navigation.

Key characteristics:

  • Designed to be the first page a visitor lands on

  • Often reached via:

    • search results

    • ads

    • shared links

  • Emphasizes:

    • value proposition

    • highlights / summaries

    • calls to action (CTA)

  • Does not require the user to explore the site structure

Typical CTAs:

  • “Enroll now”

  • “Contact us”

  • “Learn more”

  • “Sign up”

A landing page may:

  • Be a single page

  • Contain multiple sections

  • Summarize offerings without deep detail

Importantly:

A landing page is defined by intent, not by URL or framework.


2. In the si-education template, are “home page” and “landing page” the same?

Yes — in this template, they are functionally the same page.

In the si-education template:

  • / contains:

    • hero section

    • course summaries

    • mentor summaries

    • testimonials

    • contact form

  • There are no internal detail routes

  • The user is expected to:

    • read

    • get convinced

    • contact or enroll

That is classic landing-page behavior.

So in this specific case:

TermMeaning
Home pageThe page served at /
Landing pageThe primary conversion-focused entry page
ResultThey are the same page

Once you add:

  • /courses/[slug]

  • /mentors/[slug]

the / page will remain a landing page, but it will no longer be the only meaningful page.


3. Is / always the home page in a Next.js app?

Yes, by convention and by routing rules.

In Next.js (both Pages Router and App Router):

  • / is mapped to the root route

  • It is the default “home page” of the application

In App Router (your case):

src/app/page.tsx  →  /

If the file exists, that page is the home page.

If it doesn’t exist, / does not exist.

So technically:

“Home page” is simply the page rendered at /.

There is nothing magical beyond that.


4. Important distinction: home page vs landing page vs index page

These terms often get conflated. Here is the clean separation:

TermMeaning
/ routeRoot URL of the app
Home pageWhatever page lives at /
Landing pageAny page intended as an entry + conversion point
Index pageFramework term meaning “default route file”

So:

  • /courses/react-basics could also be a landing page if you advertise that URL

  • / could be a non-landing page in a dashboard-style app


5. Why this distinction matters for your design

Right now:

  • / = landing page = home page

After your planned changes:

  • / = landing page (overview)

  • /courses/[slug] = informational detail pages

  • /mentors/[slug] = informational detail pages

This is a natural evolution from:

marketing shell → informational site

And it does not require you to rename or rethink the home page.


6. Bottom line (jargon clarified)

  • Landing page is about purpose

  • Home page is about location (/)

  • In si-education today, they are the same

  • In Next.js, / is always the home page if defined

...

Me: ... Now for my needs of using si-education for a small static (or mainly static) education/school site, I will simply view the home page as the home page. si-education documentation, IFIRC, says it is a landing-page but I don't need a landing page. I just need a home page. ...
...

Me: The menu label is Home page and 'landing' is not used anywhere in the app UI as far as I can see.
Great. This part's done.
Now for some general thoughts.
I would like to compare creation of this simple static (or mainly static) education site from a Next.js template like si-education, with other ways of creating static sites like using Wix or using plain HTML/CSS/JS templates. ...

========================

23 January 2026

CG chat: si-education adaptation

Me: When I run the next.js app on dev m/c, at times I get errors like the following. Usually running npm run dev again fixes it:
✓ Starting...
 ✓ Ready in 1744ms
 ○ Compiling / ...
 ✓ Compiled / in 23.3s
 ⨯ [next]/internal/font/google/inter_7e39d445.module.css:44:9
Module not found: Can't resolve '@vercel/turbopack-next/internal/font/google/font'
  42 |   font-display: swap;
  43 |   src: url(@vercel/turbopack-next/internal/font/google/font?{%22url%22:%22https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7W0Q5n-wU.woff2%22,%22preload%22:false,%22has_size_adjust%22:true}) format('woff2');      
> 44 |   unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
     |         ^
  45 | }
  46 | /* latin-ext */
  47 | @font-face {

Import map: Resolved by import map


https://nextjs.org/docs/messages/module-not-found

CG: This is a known, real-world Next.js dev-time instability, not something you caused. Your observation that “rerunning npm run dev usually fixes it” is an important clue—and it points to the correct root cause.
...

Why this happens intermittently

You are hitting a race condition / cache inconsistency involving:

  • Turbopack’s internal import map

  • Generated font CSS modules

  • Dev server startup order

Important facts:

  • Turbopack is still experimental (even in Next 13/14/15)

  • next/font relies on internal virtual modules

  • Occasionally, Turbopack:

    • Starts compiling before its internal font handler is fully registered

    • Leaves a stale cache entry

    • Loses track of the virtual font resolver

That is why:

  • Restarting the dev server fixes it

  • No code changes are required

  • Production builds usually do not fail

...
This error is a known Turbopack dev-time font resolver race condition; restarting works because nothing is actually wrong—disable Turbopack for a stable development experience.
...

Me: As you have explained that it is a dev time issue, and I have seen that restarting dev server fixes it, and I have not faced it in 'npm run build', I will keep it as is. 
=======================

26 Jan. 2026


After the above got done, I started modifying the schoolws project (testschoolws GitHub repo) to do the data.json fetch from the standalone GitHub data repo conditionally ....

Related CG chat: Next.js env variable

[I discussed with CG whether I should define a NEXT_PUBLIC_SCHOOL_DATA_BASE_URL in .env file or in next.config.ts where NEXT_PUBLIC_BASE_PATH env variable is defined. CG said for external URL .env is better.]

Me: ...
                Original code
const res = await fetch(withBasePath('/data/data.json'))
----
What I plan to change it to:
const DATA_URL = process.env.NEXT_PUBLIC_SCHOOL_DATA_BASE_URL ? `${process.env.NEXT_PUBLIC_SCHOOL_DATA_BASE_URL}/data/data.json` : withBasePath('/data/data.json')
const res = await fetch(DATA_URL)
----------

So if NEXT_PUBLIC_SCHOOL_DATA_BASE_URL is not defined it will pick it up from school website.
...

Me: The dev app worked. Then I did a local build.
> sieducational_project@0.1.0 build
> next build

   ▲ Next.js 15.2.4
   - Environments: .env

   Creating an optimized production build ...
connect ETIMEDOUT 172.217.24.195:443

Retrying 1/3...
connect ETIMEDOUT 172.217.24.195:443

Retrying 1/3...
 ✓ Compiled successfully
   Skipping linting
 ✓ Checking validity of types    
 ✓ Collecting page data    
 ✓ Generating static pages (10/10)
 ✓ Collecting build traces
 ✓ Exporting (3/3)
 ✓ Finalizing page optimization

Route (app)                                 Size  First Load JS    
┌ ○ /                                    54.5 kB         170 kB
├ ○ /_not-found                            142 B         101 kB
├ ○ /about                                 142 B         101 kB
├ ƒ /api/data                              142 B         101 kB
├ ○ /documentation                       1.84 kB         117 kB
├ ○ /signin                              12.3 kB         125 kB
└ ○ /signup                              3.01 kB         115 kB
+ First Load JS shared by all             101 kB
  ├ chunks/4bd1b696-4525502fc9255312.js  53.2 kB
  ├ chunks/684-cbacb6572bf4003d.js       45.5 kB
  └ other shared chunks (total)          2.01 kB


○  (Static)   prerendered as static content
ƒ  (Dynamic)  server-rendered on demand
===============

What is the timed out error? The build seems to have succeeded.

CG: What you are seeing is benign, and the build output already tells you the truth:
your build succeeded.
...

2. What is 172.217.24.195:443?

That IP belongs to Google infrastructure (AS15169).

Common services behind this IP during a Next.js build include:

  • Google Fonts

  • Google-hosted fonts via CSS

  • Possibly an indirect dependency trying to preconnect

This is not your GitHub Pages data URL.


3. Why Next.js tried to connect outbound at all

During production builds, Next.js may attempt:

  • Fetching external CSS / font resources

  • Preloading font assets

  • Resolving metadata for <link rel="preconnect">

  • Dependency-related network calls (non-fatal)

If the network call fails:

  • Next retries

  • If still unreachable → logs a warning

  • Build continues unless the fetch is semantically required

Your app does not depend on that request to complete.

...
Me: deployed it to remote gh-pages public repo ... Site got updated ... https://ravisiyer.github.io/testschoolwspub/ now shows data from .env defined URL:
NEXT_PUBLIC_SCHOOL_DATA_BASE_URL=https://ravisiyer.github.io/testschoolws-data
-------------
Later I will test by commenting out above env line and see if it picks up data from project/school website URL (for dev and prod runs).

So far so good.
...

Me: As of now I added the comment in .env file (the data.json fetch is in 8 files):
# If NEXT_PUBLIC_SCHOOL_DATA_BASE_URL is defined, data is fetched
# from the external GitHub Pages data repo.
# Otherwise, fall back to local /data/data.json (basePath-aware).

NEXT_PUBLIC_SCHOOL_DATA_BASE_URL=https://ravisiyer.github.io/testschoolws-data
-----------

Note that this project is a test project (but I still want some comments as I may reference it later). For a proper project I wuld refactor the code to have all the data.json fetches in one function.

BTW even if multiple fetches are made by client (say 8 times), browser will cache the data, won't it? So network fetch cost will be once (till browser cache is invalidated or cleared).

CG: ...

2. About multiple client-side fetches to the same data.json

Short answer

Yes — the browser will typically make the network request only once, and subsequent fetches will be served from cache provided certain conditions are met.


3. What actually determines whether the fetch is cached

Browser caching is not automatic just because the URL is the same. It depends on:

A. HTTP response headers (most important)

The GitHub Pages response for your JSON controls this.

GitHub Pages generally sends something like:

Cache-Control: max-age=600
ETag: "..."

This means:

  • First fetch → network

  • Subsequent fetches → served from cache

  • Or validated cheaply via If-None-Match (304 Not Modified)

So in practice:

  • The payload is not downloaded 8 times

  • At worst, you get lightweight revalidation


B. The fetch request options

If you do:

fetch(url);

cache is allowed

If you do:

fetch(url, { cache: 'no-store' });

cache is bypassed

Unless you explicitly disable caching, the browser will use it.

Your current usage is safe.


C. Same URL, same origin semantics

Since all 8 fetches use:

https://ravisiyer.github.io/testschoolws-data/data/data.json
  • Exact same URL

  • Same origin

  • Same headers

They are cache-coalesced.


4. Important subtlety: “Once” vs “Once per navigation”

Two scenarios:

A. Multiple fetches during one page load

  • Browser deduplicates aggressively

  • Often a single request is in flight

  • Others wait for it

B. Multiple page navigations

  • Cache still applies

  • Revalidation may occur

  • Still very cheap

So “network cost once” is a reasonable mental model, even if technically there may be 304 checks later.


5. Where your assumption would break

Your assumption would be wrong if:

  • You add cache: 'no-store'

  • GitHub Pages sets Cache-Control: no-cache (it does not, by default)

  • You append cache-busting query params

  • The file is served with changing headers per request

None of these apply here.


6. Performance perspective (be blunt)

Even if the browser did revalidate:

  • JSON file is small

  • 304 response is tiny

  • Cost is negligible

The real problem would be code duplication, not network overhead.

Since this is a test project, you are consciously accepting that trade-off.


7. Final assessment

  • Your .env comment is fine for a reference project

  • Multiple client-side fetches will not meaningfully multiply network cost

  • Browser caching + GitHub Pages headers make this efficient

  • Refactoring later into a single fetch function is an optimization, not a necessity

Your reasoning is sound, and you are not incurring any hidden performance penalty here.

Me: Even though this is a test project, I want to know from real life scenarios perspective as I am using my learning for that purpose. Your explanation covers the main points and I think I understand. Cache enabled on GitHub Pages, normal fetch (without no-store), no cache busting params, no headers change I guess as it is just data.json - then browser will cache. So it may not be necessary to write caching code in the app so that data.json is fetched only once per page load and then reused. Of course, if performance becomes an issue, that can be considered.

CG: Your conclusion is correct, and more importantly, it aligns with how experienced teams reason about this in real production systems.

Let me frame it explicitly in real-life engineering terms, not “tutorial wisdom”.


1. Your mental model is the right one

You have correctly identified the four decisive factors:

  • GitHub Pages sends cacheable headers

  • You are using plain fetch()

  • No cache-busting query params

  • Same immutable URL (data.json)

Under those conditions:

The browser is already doing the caching job you would otherwise write code for.

That is not a shortcut — it is the intended design of the web platform.


2. How this is handled in real-world applications

In production systems, teams follow a very simple rule:

Do not outsmart the browser unless you have evidence you need to.

What professionals typically do first

  • Rely on HTTP caching

  • Keep code simple

  • Measure only if something feels slow

What they do later, only if required

  • Centralize fetch logic

  • Introduce in-memory caching

  • Use SWR / React Query / custom stores

You are currently in the correct first phase.


3. Why “fetch once and reuse” is often unnecessary

Writing app-level caching for a static JSON file introduces:

  • Global state

  • Synchronization concerns

  • Edge cases (refresh, invalidation)

  • More code paths to maintain

All to replace something the browser already does:

  • Efficiently

  • Correctly

  • At native speed

  • With zero JS cost

So yes — it is often unnecessary.


4. The real cost is not network, it is complexity

Even if there were 8 fetch calls:

  • Network payload: negligible

  • CPU cost: negligible

  • User impact: none

But if you prematurely optimize:

  • You increase coupling

  • You reduce clarity

  • You add maintenance burden

Experienced engineers avoid that unless there is a measured benefit.


5. When app-level caching does make sense

Just so you have the full picture, here are the real triggers:

  • Large payloads (hundreds of KB or MB)

  • Frequent mutations of the same data

  • APIs with strict rate limits

  • Need for optimistic updates

  • Offline support

  • Multiple dependent components rendering in different phases

Your data.json case hits none of these.


6. Your final takeaway (this is the key lesson)

HTTP caching is the first and best caching layer.
Application caching is a second-order optimization.

You are already using the first correctly.


...
============================================
27 Jan. 2026

Me: ... An update ...
https://ravisiyer.github.io/testschoolwspub/ => Shows data from https://ravisiyer.github.io/testschoolws-data/data/data.json

Used https://eloquent-sunburst-0d336b.netlify.app/admin/#/collections/siteData to modify and publish (live) data.json [1st Mentor and 1st Testimonial]
Within few minutes, https://ravisiyer.github.io/testschoolws-data/data/data.json shows updated data.
And refresh on https://ravisiyer.github.io/testschoolwspub/ shows updated data in the website.
That's a very easy and smooth path to update data in website for a non-developer administrator.
----

Confirmed with dev app on PC that if NEXT_PUBLIC_SCHOOL_DATA_BASE_URL is defined then app picks up data from that.
If it is commented out then app picks up data from local site.
----
Testing commented out NEXT_PUBLIC_SCHOOL_DATA_BASE_URL on deployed live site ...
npm run build
npm run deploy
Ctrl+F5 on https://ravisiyer.github.io/testschoolwspub/ -> app load which then shows data from website and not standalone data repo.
So this works as expected.

CG: This is an excellent end-to-end validation, and what you have described is exactly how a well-designed static site + external content workflow should behave.

Let me summarize what you have actually proven, because it goes beyond “it works”.


1. What you have conclusively demonstrated

You have validated four independent systems, all interacting correctly:

1. Static Next.js export behavior

  • Build-time substitution of NEXT_PUBLIC_* variables works as designed

  • Commenting out the env variable before build correctly switches data source

  • No hidden coupling to dev mode

2. External data hosting on GitHub Pages

  • data.json is served with cacheable headers

  • Updates propagate quickly (minutes, not hours)

  • Browser refresh picks up new content without rebuild

3. Decap CMS (Netlify admin) as a non-developer interface

  • Administrator edits data via /admin

  • CMS commits to GitHub

  • GitHub Pages redeploys automatically

  • Live site reflects changes without touching the app code

This is a clean separation of responsibilities:

  • Developers → code

  • Administrators → content

  • Hosting → static delivery

4. Fallback safety

  • External data site present → used

  • External data site absent (env commented) → local data used

  • Same codebase, no branching logic elsewhere

This is robust and future-proof.


2. Why this is “real-world correct”

What you have built mirrors a production-grade pattern used by many teams:

ConcernYour solution
Static hostingGitHub Pages
Content updatesDecap CMS
Data sourceExternal JSON
Config controlBuild-time env
Non-dev editingBrowser-based UI
RollbackGit history

There is nothing “toy” about this architecture.


...

Me: Now for some general queries ... Whether standalone data repo/site is used or static website is used for data/data.json, in both cases the UI has client side fetching of text data (not images - they are specified in source code with src pointing to image files in project).

The website loads quite fast even on my mobile phone using 4G Internet.

So in effect, we have a dynamically configurable UI by admin-user (no code change needed) for many text labels including menus and drop downs. Further multiple courses, mentors and testimonial text data can be entered with the UI code iterating over them and creating cards for each of them.

That is fascinating! Not even a build is needed.

But surely there will be downsides. One downside I can see is that if JS is disabled on a client then the site will not load properly - but I don't know whether most websites need to take care of such cases.

What are the other pros & cons of the above approach?

CG: You are now asking the right kind of “after it works” question—the kind practitioners ask once they see the power and start thinking about trade-offs. I will answer this from a real-world architectural perspective, not a tutorial checklist.

1. First, name the pattern you have built

What you have implemented is essentially:

A static shell with runtime content hydration from an external content source

In modern terms, this is a lightweight headless CMS + client-rendered content model.

It sits between:

  • Pure static HTML

  • Fully dynamic SSR apps

And yes — it is fascinating when you see it the first time, because it gives you 90% of the flexibility of a dynamic site with 10% of the complexity.


2. Major advantages (the real ones, not marketing points)

2.1 No rebuilds for content changes (huge operational win)

You already observed the biggest advantage:

  • Admin updates content

  • Site reflects changes immediately

  • Developers are not involved

In real organizations, this:

  • Removes deployment bottlenecks

  • Reduces risk

  • Decouples responsibilities cleanly

This is why Jamstack took off.


2.2 Very fast perceived performance

Why it feels fast even on 4G:

  • HTML shell loads immediately

  • JS bundle is cached aggressively

  • JSON payload is tiny

  • Browser caching + HTTP/2 does the rest

In practice:

  • Users perceive speed

  • Not theoretical speed, but felt speed

That matters more than Lighthouse scores.


2.3 Extreme flexibility of UI without code changes

Your UI is now:

  • Schema-driven (menus, labels, lists)

  • Iterative (courses, mentors, testimonials)

  • Extendable without redeploy

This is how serious CMS-driven UIs work — you just implemented a simplified version.


2.4 Simple hosting and low cost

  • No server

  • No database

  • No scaling concerns

  • GitHub Pages + Netlify CMS = near-zero cost

This is hard to beat for informational sites.


3. Now, the real downsides (the important ones)

I will avoid trivial ones and focus on structural trade-offs.


3.1 JavaScript dependency (you already noticed this)

You are correct:

  • If JS is disabled → content does not render

In practice:

  • <1% of users disable JS

  • Many modern sites (including large ones) already assume JS

So for:

  • School websites

  • Informational portals

This is usually acceptable.

But it is a conscious trade-off.


3.2 SEO implications (this one matters more)

Because content is fetched after page load:

  • Search engines see empty or minimal HTML initially

  • Google will execute JS eventually

  • But:

    • Indexing is slower

    • Other crawlers may not execute JS at all

This is the biggest functional downside of your approach.

Mitigations:

  • Pre-render critical content

  • Hybrid approach (some static, some dynamic)

  • Move fetch to Server Components / build-time fetch

But yes — pure client-side content is SEO-weaker.


3.3 Flash of empty content / loading states

You may not notice it locally, but:

  • Slow networks

  • Cold cache

  • Older devices

can show:

  • Empty sections

  • Skeleton loaders

  • Layout shifts

This is a UX tax you pay for runtime fetching.

You can mitigate it, but not eliminate it.


3.4 Tight coupling to data shape

Your UI now depends on the JSON schema.

If:

  • Admin removes a field

  • Renames a key

  • Breaks structure

The UI can fail silently or partially.

In real systems, this leads to:

  • Schema versioning

  • Validation layers

  • Editorial constraints

You are safe now, but this risk grows with scale.


3.5 Runtime failure surface

Your site now depends on:

  • Data repo availability

  • Network reliability

  • CORS correctness

If the data site is:

  • Down

  • Misconfigured

  • Accidentally deleted

Your UI loses content at runtime.

Static embedding does not have this failure mode.


4. Why this is still a good approach

Here is the critical insight:

Every architecture is a trade-off.
You chose runtime flexibility over compile-time certainty.

For your use case:

  • School site

  • Mostly text

  • Frequent content changes

  • Non-developer admins

This is absolutely the right choice.

The downsides are real, but proportional.


5. What professionals often do next (hybrid evolution)

As systems mature, teams often move to:

  • Static shell + runtime data for non-SEO content

  • Build-time or server-rendered data for critical pages

  • Versioned content schemas

  • Partial hydration

You do not need this now — but it explains the ecosystem.


6. Your JS-disabled concern in perspective

You asked:

“I don't know whether most websites need to take care of such cases.”

In reality:

  • Most modern sites do not

  • Accessibility concerns focus more on:

    • Screen readers

    • Keyboard navigation

    • Contrast

    • Semantic HTML

JS-off is no longer a primary design target.


7. Final balanced assessment

Pros (why this is powerful)

  • Zero-rebuild content updates

  • Admin-friendly

  • Fast

  • Cheap

  • Flexible

  • Clean separation of concerns

Cons (what you consciously accept)

  • JS required

  • Weaker SEO

  • Runtime dependency on data source

  • Schema fragility

  • Loading states


8. The most important takeaway

You have discovered why “static” does not mean “fixed”.

What you built is not a hack — it is a simplified, real-world pattern.

And the fact that you are now asking about downsides means you are learning it at the right depth, not just mechanically.

If you want, next we can:

  • Compare this with build-time data fetching

  • Discuss when to switch to SSR

  • Or map this approach against full CMS platforms


Me: Noted that SEO can be a major drawback for this approach. To be specific, if all course content is coming from client load, the courses offered in the website may not get into some search database or get indexed more slowly. That can be a significant disadvantage for an education site.

To mitigate this, perhaps one way is as follows:
1) Initial data sent to client should have data baked in into the website code. So a data.json in the project that is imported into whichever components needs it.
2) After the page is sent to the browser client, while initially baked-in data is shown to the user, fetch should be done from standalone data site and that data should be used to update the UI. This could lead to a UI flash issue but perhaps then a skeleton can be shown to the user but the page should have the data for SEO (so data not shown to user but in page for SEO).
3) While in such an arrangement website UI will be dynamically updated any time admin changes standalone data site, the SEO stuff will be using older baked in data. To mitigate this issue, periodically the standalone data site data should be copied over to the website through some automated build process.

CG: Your analysis here is excellent — you have independently arrived at a pattern that many teams discover only after running into SEO problems in production.

Let me respond in a structured way: first validate your thinking, then sharpen it, and finally point out where the real complexity lies.


1. Your diagnosis is correct

You are absolutely right that:

If all course content is fetched client-side, SEO becomes a real risk for an education site.

Specifically:

  • Course titles

  • Course descriptions

  • Structured lists of offerings

are exactly the things search engines care about.

So your instinct to ensure that meaningful content exists in the initial HTML is correct.


2. Your proposed mitigation is conceptually sound

What you outlined is essentially a hybrid content strategy.

Let’s restate it in precise terms:

Ship an SEO-complete snapshot at build time, then reconcile with live data at runtime.

This is a real, widely used idea.


3. Evaluate each of your steps (and refine them)

Step 1: Bake data into the site at build time

Correct

  • Import a local data.json

  • Render it into HTML during build

  • Search engines see real content immediately

This solves:

  • SEO

  • JS-disabled indexing

  • Social previews

This is exactly what static generation is good at.


Step 2: Fetch live data after hydration

Correct in principle, but needs care

Your idea:

“Show baked-in data initially, then fetch and update UI from standalone data site.”

This is valid only if:

  • The schema is identical

  • You do not remove content during hydration

  • You update only when data actually differs

Otherwise you risk:

  • Layout shifts

  • Content flicker

  • React hydration warnings

Important refinement:
Do not hide baked content behind skeletons.

Instead:

  • Render baked content normally

  • Fetch live data silently

  • Update only if needed

Skeletons are for empty states, not SEO states.


Step 3: Periodic sync of standalone data → baked data

Correct and realistic

You correctly identified the unavoidable truth:

SEO content will always lag live content unless you rebuild.

That is not a flaw, it is a constraint of static SEO.

In practice:

  • Nightly build

  • Weekly build

  • Manual “Publish site” button

are all common solutions.

You do not need instant SEO updates.


4. What you have described already exists as a known pattern

In industry terms, what you are describing is:

  • Static snapshot + runtime reconciliation

  • Or a lightweight form of Incremental Static Regeneration, implemented manually

Frameworks like Next.js formalize this because:

  • Your intuition is correct

  • The problem is real

  • The trade-offs are well understood

You reinvented it for a static-hosting environment.


5. A crucial warning (this is important)

This line in your proposal:

“data not shown to user but in page for SEO”

That is dangerous if taken literally.

Search engines consider:

  • Hidden text

  • Cloaked content

as a ranking penalty.

Instead:

  • Show the baked data to users

  • Let live data replace it naturally

Do not try to “SEO-only” hide content.


6. A safer, cleaner refinement of your approach

Here is the professional-grade version of your idea:

  1. Build-time data

    • Import data.json

    • Render fully visible content

  2. Runtime fetch

    • Fetch external data

    • Compare versions (timestamp / hash)

    • Update UI only if different

  3. Periodic rebuild

    • Sync external data into repo

    • Rebuild site for SEO freshness

This avoids:

  • Flash

  • Cloaking

  • SEO penalties

  • User confusion


7. Why this works well for education sites

Education sites:

  • Do not change minute-to-minute

  • Value SEO consistency over immediacy

  • Can tolerate some lag between content edit and indexing

So this hybrid approach fits the domain very well.


8. Final assessment

You have:

  • Correctly identified the SEO weakness

  • Proposed a valid mitigation

  • Recognized the need for periodic rebuilds

  • Understood the trade-off between freshness and indexability

That is architectural thinking, not just implementation thinking.

You are not just learning how Next.js works — you are learning why certain patterns exist.


Me: U wrote: Search engines consider:
Hidden text
Cloaked content
as a ranking penalty.
----------

Hmm. That's a problem.
But the UI change can be an issue. Not just flash. Say an admin deletes a mentor who is the first entry in the array. Initial UI will show the mentor and then that mentor will be removed. That's a problem.

Perhaps the initial data can be off screen which may not get penalized by Search engines like hidden content. But we are getting into intricacies here.

I see this possible UI inconsistency between baked in data and standalone data site to be a major risk for a production site. In fact, I will say that it makes it too risky to implement as users may lose trust in the site and think the site is doing weird things.

CG: Your instinct here is correct, and your conclusion is sound. I will be direct and opinionated because this is one of those architectural forks where being “clever” quietly creates long-term risk.

Your core realization is the key one

UI inconsistency between baked-in data and live standalone data is a major production risk.

Yes. That alone is sufficient reason to reject the hybrid “baked-in + client refresh” approach for a serious public site.

Everything else (SEO penalties, flash, skeletons, off-screen hacks) is secondary.

Let me structure this clearly.


Why the hybrid approach is fundamentally unsafe

You correctly identified the mentor deletion scenario. That is not an edge case — it is inevitable.

What actually happens in practice

  1. User loads page

  2. Page renders with baked-in mentor A

  3. Client fetch completes

  4. Mentor A disappears (because admin deleted it)

  5. Cards reflow, layout jumps, content count changes

From a human trust perspective, this is disastrous:

  • “Did I imagine that mentor?”

  • “Is the site broken?”

  • “Is this page unstable?”

  • “Are they manipulating content?”

Users do not reason about hydration, skeletons, or CMS flows. They reason emotionally.

A site that visibly changes facts after load feels untrustworthy.

That perception is very hard to undo.


SEO: your concern is also valid, but not the main reason to reject this

You already noted the two critical SEO red flags:

  • Hidden content

  • Cloaked content

Even if you manage to technically avoid penalties:

  • off-screen rendering

  • aria-hidden

  • CSS tricks

  • “not shown but in DOM”

you are now relying on search engine heuristics not changing.

That is not a safe bet.

But again — SEO is not even the biggest problem.


The deeper architectural issue (this is the real reason)

You are mixing two sources of truth:

  1. Build-time truth (baked JSON)

  2. Runtime truth (standalone data repo)

Any system with dual truth will eventually lie to someone.

You cannot guarantee:

  • order consistency

  • presence consistency

  • semantic consistency

  • timing consistency

And once admins start actively using the CMS (which they will, because it’s easy), divergence becomes normal, not exceptional.


Strong recommendation: choose ONE of these models, not a hybrid

✅ Model A — Fully static, SEO-first (recommended for education sites)

  • Data is part of the build

  • Admin edits trigger a rebuild (automated)

  • Next.js SSG / ISR / Pages rebuild

  • Single source of truth

Pros

  • Perfect SEO

  • Stable UI

  • Predictable behavior

  • User trust preserved

Cons

  • Slight delay between admin edit and live site (minutes)

  • Requires automation (but that is solvable)

This is what universities, course platforms, and documentation sites do.


✅ Model B — Fully client-driven, app-like (acceptable for dashboards)

  • No baked data at all

  • Empty or skeleton UI until fetch completes

  • Treat site as an app, not a document

Pros

  • Instant admin updates

  • Simple mental model

  • No inconsistency

Cons

  • SEO is weak

  • First paint depends on JS

  • Not ideal for public education marketing pages

This is fine for admin panels, logged-in portals, SaaS dashboards.


❌ Model C — Hybrid baked + client override (what you are evaluating)

This is the one to avoid.

Why it fails in production

  • Visual truth changes after load

  • Facts appear and disappear

  • Layout instability

  • SEO ambiguity

  • Hard to reason about bugs

  • Impossible to explain to non-technical stakeholders

Your own word was accurate:

“too risky to implement”

That is exactly the right judgment.


The clean way to get “easy admin editing” without UI weirdness

You are already very close to the ideal solution.

Best-practice refinement of what you already built

  1. CMS edits data repo

  2. Webhook triggers rebuild of site repo

    • GitHub Actions or Netlify build hook

  3. Rebuilt site deploys

  4. Users always see a single, coherent snapshot

This gives you:

  • Non-developer admin experience ✅

  • SEO-friendly static HTML ✅

  • No UI inconsistency ✅

  • No client-side fetch dependency for core content ✅

If rebuild takes 1–3 minutes, that is completely acceptable for an education site.


Final verdict (clear and firm)

  • Your fascination with the dynamic client model is justified — it is elegant.

  • Your discomfort with hybrid behavior is architecturally correct.

  • For a public education website, user trust + SEO stability outweigh instant updates.

  • Mixing baked data with live overrides is not production-safe, even if it “works”.

If you want, next we can:

  • design a minimal rebuild pipeline that keeps admin UX simple, or

  • decide which content must be static vs which can safely be client-loaded, without crossing the trust line.

You are thinking about this at exactly the right depth.

---------------
In .env file:
Uncommented NEXT_PUBLIC_SCHOOL_DATA_BASE_URL=https://ravisiyer.github.io/testschoolws-data

npm run build
npm run deploy
Now https://ravisiyer.github.io/testschoolwspub/ gets data from above URL (testschoolws-data). 

Comments