Skip to content

SSG can't generate separate HTML per param instead of a single template with client-side hydration #671

@taehee-ms

Description

@taehee-ms

Summary

When using +ssg with dynamic routes (e.g. [lang]) and generateStaticParams(), the framework currently generates only one HTML file per route pattern—using one arbitrary param value—rather than pre-rendering a separate HTML file for each param combination. The client then hydrates and swaps content based on the actual URL, which causes visible flickering for users. This does not match the expected behavior of Static Site Generation eco system.

Environment

  • One.js version: 1.4.11
  • Deployment: Vercel (using .vercel/output structure)
  • defaultRenderMode: 'spa'

Current Behavior

Build output structure

For a route like app/[lang]/index+ssg.tsx with:

export async function generateStaticParams() {
  return [{ lang: 'en' }, { lang: 'ko' }];
}

The build produces:

.vercel/output/static/
├── :lang/                    # ← Single folder with param placeholder, not en/ or ko/
│   ├── index.html
│   └── ...
├── en/
│   └── terms-and-conditions.html # ← Explicit routes correctly get per-lang HTML
└── ko/
    └── terms-and-conditions.html
  • Dynamic [lang] routes → output under :lang/ with a single HTML per path
  • Explicit en/ / ko/ routes → correct per-param HTML under en/ and ko/

User experience

  1. User visits /ko/
  2. They receive the same :lang/index.html, rendered with one arbitrary param (e.g. lang: "en")
  3. The initial HTML shows content for that single param (e.g. English)
  4. Client-side hydration fetches/uses the correct param from the URL
  5. The DOM is then updated to match the actual Param (like Korean)
  6. Result: Visible content flash/flicker as the page switches from one language to another

Expected Behavior

For SSG with generateStaticParams(), the framework should:

  1. Pre-render HTML for each param combination returned by generateStaticParams()
  2. Output separate static files per param, e.g.:
    static/en/index.html
    static/ko/index.html
    static/en/adhd-survey/index.html
    static/ko/adhd-survey/index.html
    ...
    
  3. Avoid relying on client-side hydration to “fix” the content—the initial HTML should already match the requested URL.

Why this matters

  • SSG definition: Static Site Generation typically means HTML is fully generated at build time for each static path. A single template filled in by the client is closer to a CSR/SPA pattern.
  • UX: Flickering between languages (or other param-dependent content) is confusing and looks broken.
  • SEO: While crawlers may eventually see the correct content after JS runs, the initial HTML is wrong, which can affect indexing and Core Web Vitals.
  • Accessibility: Users with slow connections or limited JS may see incorrect or incomplete content.

Suggested approach

During the SSG build, for each route that exports generateStaticParams():

  1. Call generateStaticParams() to get the param combinations
  2. For each combination, render the route with those params
  3. Emit a distinct HTML file per param (e.g. en/index.html, ko/index.html) instead of a single :lang/index.html

The behavior can be configured.

Comparison with explicit routes

Explicit routes like app/en/privacy-policy+ssg.tsx and app/ko/privacy-policy+ssg.tsx already produce correct per-param output. The same behavior can apply to dynamic routes when generateStaticParams() is defined. (by configuration?)


Thank you for maintaining One.js. I’d be happy to provide more details or a minimal repro if helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions