Repository navigation
Fix SSR SEO blank output and hydration errors - #779
augustmood wants to merge 5 commits into
Conversation
|
@augustmood is attempting to deploy a commit to the deltaDAO Team on Vercel. A member of the Team first needs to authorize it. |
|
Hey @augustmood, thank you very much for your work here! I am not sure if this PR is ready for review / merge, as it is still in draft. Just in case you missed that, please mark as ready for review and we'll get back with feedback. |
Hey @moritzkirstein, Sorry for the delayed response. I initially thought the PR was ready, but I identified some issues and bugs, so I have marked it as a draft. I will address these problems and mark it as ready for review once everything is resolved. I expect it to be ready by next week. |
- Replace `router.route` with `router.pathname` across pages to use SSR‑safe
template URIs (index, verify, 404, search, profile, bookmarks,
asset/[did], publish/[step], privacy/[slug], cookies/[slug], [slug]).
- Make URL param parsing SSR‑safe:
- Search Filter/Sort build `search` from `window.location.search` on client,
with a `router.asPath` fallback on server, instead of direct `location.search`.
- Profile/History defaultTab parsed from `URLSearchParams` over SSR‑safe `search`.
- Prevent header hydration mismatches:
- Render `Wallet` and `NetworkMenu` client‑only (`ssr: false`) to avoid
wagmi state differences between server and client.
- Harden browser‑only access and cookies utility:
- Guard all `window`/`location` access with `typeof window !== 'undefined'`.
- Use `secure` cookie flag and avoid direct `location` reads in `cookies.ts`.
- UserPreferences hydration for SSR stability:
- Initialize `allowExternalContent` and `chainIds` with SSR‑stable defaults,
then hydrate from cookies after mount.
- Tooltip stability:
- Only set `appendTo=document.body` in the browser to keep SSR/CSR markup aligned.
Notes
- No changes to data‑fetching strategy; CSR behavior remains unchanged.
- Canonical tag generation now uses SSR‑stable URIs via `pathname` to avoid
querystring leaks and hydration issues.
Signed-off-by: Binming Li <binming.li@outlook.com>
5203685 to
2a70075
Compare
…unify publish canonical
- SSG pages: build canonical from props.slug (no router)
- privacy/[slug], cookies/[slug], [slug] now use `uri` derived from `page.slug`
- Ensures SSR/CSR identical output; precise canonical without query
- Asset details (asset/[did]): add GSSP to SSR-inject real path
- `getServerSideProps` returns `{ ssrUri: `/asset/${did}`, did }`
- Page uses `ssrUri` for `<Seo>` canonical (no query), `did` for providers
- Data fetching remains CSR; only canonical path is SSR-stable
- Publish steps (publish/[step]): add GSSP and unify canonical
- `getServerSideProps` returns `{ ssrUri: '/publish' }` for all steps
- Consolidates SEO weight on the entry page; avoids step-level duplicates
Notes
- Canonical URIs no longer include querystrings
- View Page Source now shows precise canonical for asset pages (and unified for publish)
Signed-off-by: Binming Li <binming.li@outlook.com>
|
Hey @moritzkirstein, Thanks for your patience! I’ve fixed the issues and the PR is now marked as ready for review. Please let me know if you have any feedback once you’ve had a chance to take a look. |
- Restore SSR for Faucet page and keep canonical stable via router.pathname.
- Render the card on first paint; avoid hydration mismatch from wagmi state by
rendering placeholders until mount:
- Show "—" for address/network on SSR and the first client render.
- Replace with actual values after mount; disable the button until mounted
and an address is present.
- Ensures no hydration errors while preserving immediate UI render.
Files: src/pages/faucet.tsx, src/components/Faucet/index.tsx
Breaking changes: none
Signed-off-by: Binming Li <binming.li@outlook.com>
…ount - Show "completed" checkmarks only for steps BEFORE the current step and only when that step has no validation errors. - Enable Formik validateOnMount so initial errors are computed on first render. - Prevents steps 2/3 from showing as checked on initial page load. Files: src/components/Publish/Navigation/index.tsx, src/components/Publish/index.tsx Breaking changes: none Signed-off-by: Binming Li <binming.li@outlook.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Hey @augustmood, Thanks a lot for the updates, we really appreciate your contribution! 🙌 This PR is quite big, so it’ll take us a little time to go through it thoroughly. It’s definitely on our radar and we’ll get back to you with feedback as soon as possible. Thanks for your patience in the meantime! |
There was a problem hiding this comment.
Pull request overview
This PR fixes server-side rendering (SSR) issues that prevented SEO metadata from appearing in the HTML source, caused hydration mismatches, and generated incorrect canonical URLs. The changes ensure complete SEO markup is rendered server-side, stabilize component hydration, and establish consistent canonical URIs across all pages.
Changes:
- Initialize URQL client at module load to enable SSR rendering instead of in useEffect
- Replace
window/locationaccess in SEO and cookie utilities with SSR-safe alternatives - Standardize canonical URI generation using
router.pathnamefor static pages andgetServerSidePropsfor dynamic routes - Make Wallet and NetworkMenu client-only components, add mounted guards to Faucet, and compute URL parameters SSR-safely in Search and Profile
- Move HTML lang attribute from Head to _document.tsx and add validateOnMount to Publish form
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/@context/UrqlProvider.tsx | Initialize URQL client at module load for SSR availability |
| src/@utils/subgraph.ts | Create separate query client decoupled from Provider |
| src/components/@shared/Page/Seo/index.tsx | Derive environment from siteUrl instead of window.location |
| src/pages/_document.tsx | Add Html lang="en" wrapper in _document |
| src/pages/asset/[did]/index.tsx | Inject SSR-stable URI via getServerSideProps |
| src/pages/publish/[step].tsx | Unify all publish step canonicals to /publish |
| src/pages/{verify,search,profile,index,404,bookmarks,faucet}.tsx | Change to router.pathname for canonical |
| src/pages/{privacy,cookies}/[slug].tsx | Construct canonical from slug prop |
| src/pages/[slug].tsx | Use page.slug for canonical |
| src/components/Header/Menu.tsx | Load Wallet and NetworkMenu client-only |
| src/components/@shared/atoms/Tooltip/index.tsx | Guard appendTo with typeof window check |
| src/components/Search/{Filter,sort}.tsx | Parse URL params SSR-safely |
| src/components/Faucet/index.tsx | Add mounted state to prevent hydration mismatch |
| src/components/Profile/History/index.tsx | Parse search params SSR-safely |
| src/components/Publish/Navigation/index.tsx | Only show checkmarks for past steps |
| src/components/Publish/index.tsx | Add validateOnMount to Formik |
| src/@utils/cookies.ts | Check window availability before accessing location |
| src/@context/UserPreferences.tsx | Initialize state SSR-safely and hydrate from cookies on mount |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export async function getServerSideProps({ | ||
| params | ||
| }: { | ||
| params: { step: string } | ||
| }): Promise<{ props: { ssrUri: string } }> { |
There was a problem hiding this comment.
The params.step parameter is received but never used in the function. Since the canonical URI is unified to /publish for all steps, this parameter should either be used (if different steps need different handling) or the type should acknowledge it's intentionally unused. Consider removing it from the destructuring or adding a comment explaining why it's unified to /publish.
| export async function getServerSideProps({ | |
| params | |
| }: { | |
| params: { step: string } | |
| }): Promise<{ props: { ssrUri: string } }> { | |
| export async function getServerSideProps(): Promise<{ props: { ssrUri: string } }> { |
| export async function getServerSideProps({ | ||
| params | ||
| }: { | ||
| params: { step: string } | ||
| }): Promise<{ props: { ssrUri: string } }> { | ||
| return { | ||
| props: { | ||
| ssrUri: `/publish` | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The getServerSideProps function should use Next.js's GetServerSideProps type instead of inline type annotations. Import GetServerSideProps from 'next' and type it as GetServerSideProps<{ ssrUri: string }> for better type safety and consistency with Next.js conventions.
| export async function getServerSideProps({ | ||
| params | ||
| }: { | ||
| params: { did: string } | ||
| }): Promise<{ props: { ssrUri: string; did: string } }> { | ||
| const { did } = params | ||
| return { | ||
| props: { | ||
| ssrUri: `/asset/${did}`, | ||
| did | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The getServerSideProps function should use Next.js's GetServerSideProps type instead of inline type annotations. Import GetServerSideProps from 'next' and type it as GetServerSideProps<{ ssrUri: string; did: string }> for better type safety and consistency with Next.js conventions.
fix(ssr/seo): Stabilize SSR rendering and canonical URIs; inject SSR‑safe URIs for dynamic routes
Primary goal: fix SEO so “View Page Source” shows the complete server‑rendered SEO markup. This PR restores stable SSR output, corrects canonical generation, and removes browser‑only dependencies during SSR. It also consolidates publish step canonicals and injects SSR‑stable URIs for dynamic routes.
Summary
router.pathnameor server‑injectedssrUri), never fromasPath/query strings./publish.windowusage in render paths; guard browser‑only code and stabilize components to prevent hydration mismatches.Context & Root Cause
useEffectand rendering was gated behindclient ? <Provider> : <></>. During SSR,useEffectdoes not run, resulting in an empty render that stripped all SEO from the server output.window.locationto decide robots/twitter meta, producing different<Head>markup on server vs client and triggering hydration mismatches.<html lang>lived inside<Head>instead of_document.tsx, contributing to divergent markup and hydration warnings.Changes
{children}to avoid an empty SSR tree.@utils/subgraphto decouple from Provider at SSR time.windowaccess during render; derive environment fromsiteUrland make Head deterministic on the server.<Html lang="en">topages/_document.tsx.router.pathnameinstead ofroute/asPathand build canonical from props for static content.getServerSidePropsfor dynamic routes (asset) and unify publish steps to/publish.appendToin the browser.router.pathname.validateOnMountso initial errors are computed on first render.Verification & Impact
/asset/${did}(no "?" query part), e.g./asset/did:op:abc123…:src/pages/asset/[did]/index.tsx:1./publish:src/pages/publish/[step].tsx:1.robotsmeta:portal.pontus-x.euallows indexing.noindex,nofollow.secureand without directwindow/locationduring SSR.<title>,<meta>, Open Graph, Twitter) so “View Page Source” reflects the full content.Scope and Risk
Related Commits