Skip to content

Conversation

anthonybailey
Copy link
Collaborator

We want non-localized routes to be handled dynamically via redirects, and
all content localized to xx to be static under the URL xx/some-page.
We expect this to work best with Netlify, and be least confusing.

The Paraglide 2.0 framework needs both prefix-all-locales as a
routing strategy and also for us to specify all patterns so as to force /en/
as a prefix - the default patterns don't truly honor prefix-all-locales.

Implementation in writeSettingsFile() since it has the final locale list.

Since visiting a URL now sets the locale cookie, also added a visual effect
to the locale switcher button so folk can see when this has occurred.

Also includes a bugfix: recent LLM message translations had added commentary
around the JSON object, which meant Paraglide couldn't parse those messages.
I've implemented protection against this at last responsible moment (before
compiling) but can move it earlier when we work more on LLM improvements.

…e same

We want non-localized routes to be handled dynamically via redirects, and
 all content localized to xx to be static under the URL xx/some-page.
We expect this to work best with Netlify, and be least confusing.

The Paraglide 2.0 framework needs both prefix-all-locales as a
 routing strategy and also for us to specify all patterns so as to force /en/
 as a prefix - the default patterns don't truly honor prefix-all-locales.

Implementation in writeSettingsFile() since it has the final locale list.

Since visiting a URL now sets the locale cookie, also added a visual effect
 to the locale switcher button so folk can see when this has occurred.

Also includes a bugfix: recent LLM message translations had added commentary
 around the JSON object, which meant Paraglide couldn't parse those messages.
 I've implemented protection against this at last responsible moment (before
 compiling) but can move it earlier when we work more on LLM improvements.
Copy link

netlify bot commented May 9, 2025

Deploy Preview for pauseai ready!

Name Link
🔨 Latest commit 74bd357
🔍 Latest deploy log https://app.netlify.com/projects/pauseai/deploys/682df2b17b49ce0008999015
😎 Deploy Preview https://deploy-preview-325--pauseai.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@anthonybailey
Copy link
Collaborator Author

Nils, welcome your thoughts, also planning to meet with Joep in around an hour so I'll show him too. Intend to agree with him an approach to easily previewing or otherwise testing the website mainline with locales enabled as well as the en-only state we will have in production in the meantime.

@anthonybailey
Copy link
Collaborator Author

Hmmm. I tried to force the same state onto Paraglide and generate a preview (which builds with multiple locales enabled.)

GitHub believes this works and the states are identical. I requested a preview of paraglide HEAD and from the dashboard, you'd think https://681e1534976724b3afc937b8--pauseai.netlify.app/ would be that preview.

It really isn't, though. My prefix-all-locales changes aren't evident, and the Pause Con banner looks stale.

Hmmm.

@Wituareard
Copy link
Collaborator

We also need to uncomment the code that modifies the "prerendered" field in the adapter for this to work

@Wituareard
Copy link
Collaborator

Can't inspect closer atm

@anthonybailey
Copy link
Collaborator Author

We have (for now) an l10-preview branch and preview. Needs further attention.

Anthony Bailey and others added 5 commits May 20, 2025 06:16
This commit merges changes from l10-preview and main to update
 the poending pull request.

- Filter prerendered paths to those with locale prefix
- Fix issue #333: Remove LLM commentary in translated markdowns
- Add timing and authentication diagnostics to Git operations
- Fix markdown preprocessing (missing line ending - wituaread)
The Backdrop component was missing but required by Toc.svelte.
This fixes build errors while preserving the visual functionality.

Note: There is a width issue with ToC in DE locale (German) that
should be investigated separately by the ToC feature developer.
@anthonybailey
Copy link
Collaborator Author

Both l10-preview and the prefix-all-locales pull request build successfully then 500 "Internal Server Error" with no output in logs.

Reproduces under "netlify serve" locally though so should be plausible to track down. Working on it.

@anthonybailey
Copy link
Collaborator Author

Yeah, returning src/lib/adapter-patch-prerendered.js to having no effect gets us back a functional server under both locales=en and locales=all.

But there will presumably be particular paths that Nils reckoned we did want to filter from being considered prerendered (so that they would be handled by the server: in particular any that need redirected!) I'll look more case-by-case.

@Wituareard
Copy link
Collaborator

We are missing the language negotiation logic from the previous paraglide adapter that should run under the root path

@anthonybailey
Copy link
Collaborator Author

Sure, I understand why leaving the root-equivalents marked as prerendered isn't right. It's just that the filter as-was appears to have zapped some other key route. I'll find the culprit.

@Wituareard
Copy link
Collaborator

What I mean is that we currently just don't have that logic, right? So it makes sense that there's an error

@anthonybailey
Copy link
Collaborator Author

I'm currently chasing the possibility we are invoking Netlify edge-functions in certain contexts for the first time in at least a while, and definitely in a l10n-aware context; that this involves use of Deno, which has a unique set of compatibility problems with particular Node libraries.

It's based on the fact that every edge function, however simple, causes this 500 issue if the render.js edge function is in play at all (whether or not it is called.) Suggests an initialization/imports issue that occurs when Netlify is run but doesn't manifest in any easily visible logging affordance.

@anthonybailey
Copy link
Collaborator Author

I'll paste Claude's current summary of potential issues. Still seeking ground truth on some of them.


● Runtime Compatibility Analysis Summary

Problem Identified: Edge functions returning 500 errors when adapter filter enabled, forcing routes through Deno-based edge functions instead of static prerendering.

Analysis Method:

  1. Systematic testing showed even minimal edge functions failed with 500 errors
  2. Examination of 3MB+ bundled render.js revealed potential Node.js/Deno incompatibilities
  3. Investigation of recently added l10n dependencies

Key Findings:

Environment Detection Issue:

  • Standard typeof window === 'undefined' server detection fails in Deno edge runtime
  • Deno provides a window object but lacks window.location, breaking typical browser/server detection patterns
  • Required additional check for typeof globalThis.Deno !== 'undefined' to properly identify Deno server environment

AsyncLocalStorage Incompatibility:

  • Paraglide runtime's use of AsyncLocalStorage (Node.js API) incompatible with Deno edge runtime
  • Required disableAsyncLocalStorage: true to prevent runtime failures

Dependency Incompatibilities (confirmed via web search):

  • axios explicitly documented as unsupported in Deno edge runtimes
  • simple-git designed specifically for Node.js, fails in Deno environments
  • These dependencies were added during l10n work, explaining why edge functions worked previously

Root Cause: L10n implementation introduced Node.js-specific dependencies that cannot execute in Netlify's Deno-based edge runtime, causing 500 errors when routes are forced through edge functions rather than static prerendering.

● URLs that identified axios and simple-git as problematic dependencies:

Axios incompatibility:

Simple-git incompatibility:

General edge function 500 errors with npm packages:

Edge runtime compatibility guidance:

These URLs specifically documented that axios is unsupported in edge runtimes and that simple-git has Node.js-specific dependencies that fail in Deno environments.

@Wituareard
Copy link
Collaborator

We use edge functions in plenty of spaces, almost entirely sure it's just that the logic for handling the requests is missing

anthonybailey pushed a commit that referenced this pull request May 30, 2025
In #325 I'm looking at
 Deno vs Node compatibility issues. In that context, looks like the
 paraglide compilation options could benefit from these changes to
 disable async local storage, and know that window can be defined even
 on the server when running under Deno.

This was based on some runtime analysis and general Deno/Node.js
 compatibility requirements from docs/Git issues - see pull request.
@Wituareard
Copy link
Collaborator

@Wituareard
Copy link
Collaborator

Also, the strategy order seems off: URL should be first since if there is a prefix we won't redirect because we won't be able to since it's static

@anthonybailey
Copy link
Collaborator Author

Sure, url being first is the working state. l10-preview has later commits and my local box many more. Hang on in there.

@anthonybailey
Copy link
Collaborator Author

For Joep: in the course of trying to untangle why particular code changes re prerendered pages plus edge function redirects provoked 500s under Netlify (but not other previews of builds) a bunch of things we needed to fix anyway came up and interfered practically with the investigation.

We had:

  • nonlocalized links from localized pages (dumb, requires pointless redirect) that also affected whether pages get marked as prerendered (since SvelteKit crawls the site to decide that)
  • missing support for serverless as opposed to edge functions
  • hard to grok interactions between different modes of l10n and different build flavors
  • no support for doing useful l10n LLM work locally without potentially polluting the repos used for public website builds
  • and downstream, no way to stop l10n work related to previews and pull requests also affecting builds of a localized public website

I have all this fixed: just wanting to document the better workflow before imposing it on other devs.

@anthonybailey
Copy link
Collaborator Author

The better workflow is pending merge to live. In the meantime, I reproduced problems as simply as I could with the new toys in place.

Claude helped. You know how much it likes to talk, so we'll allow it to write a summary. And I'll attach too many files.

@anthonybailey
Copy link
Collaborator Author

500 Error Investigation: Edge Functions vs Serverless Functions - RESOLVED

We have completed a comprehensive investigation into the 500 errors that occur when using prefix-all-locales routing strategy. The investigation successfully reproduced and identified the root cause.

🔍 Investigation Branch & Logs

Branch: investigate/500s
Raw session: /notes/raw_chat/20250616T14.edge_vs_serverless_500_investigation.txt
Build logs: netlify-500-test.log, netlify-serverless-serve.log (multiple log files available)

🎯 Root Cause: Both Function Types Are Broken

Configuration causing issues:

  1. strategy: 'prefix-all-locales' in project.inlang/default-settings.js
  2. Prerender filtering enabled in src/lib/adapter-patch-prerendered.js
  3. Multiple locales enabled (PARAGLIDE_LOCALES=en,de)

📊 Test Results

Edge Functions (edge: true) - COMPLETELY BROKEN ❌

curl -I http://localhost:37572/faq      → 500 Internal Server Error
curl -I http://localhost:37572/proposal → 500 Internal Server Error  
curl -I http://localhost:37572/en/faq   → 200 OK (prefixed routes work)

Serverless Functions (edge: false) - PARTIALLY BROKEN ⚠️

curl -I http://localhost:37572/faq      → 200 OK (NO REDIRECT - WRONG\!)
curl -I http://localhost:37572/proposal → 200 OK (NO REDIRECT - WRONG\!)
curl -I http://localhost:37572/en/faq   → 200 OK

Critical Discovery: Serverless functions serve content directly at /faq instead of redirecting to /en/faq:

curl -w "FINAL_URL: %{url_effective}" -s -o /dev/null http://localhost:37572/faq
FINAL_URL: http://localhost:37572/faq  # Should be /en/faq

🔧 Expected vs Actual Behavior

Expected with prefix-all-locales:

  • /faq302 Redirect/en/faq (or user's preferred locale)

Actual behavior:

  • Edge functions: /faq500 Internal Server Error
  • Serverless functions: /faq200 OK with wrong URL (client-side fixup)

🧠 Technical Analysis

The fundamental issue affects both function types:

  1. SvelteKit prerenders both prefixed (/en/faq, /de/faq) AND non-prefixed (/faq) routes
  2. adapter-patch-prerendered.js filters out non-prefixed routes from prerendered paths
  3. When requests arrive for non-prefixed routes:
    • Edge functions (Deno runtime): Crash with 500 errors
    • Serverless functions (Node.js runtime): Serve content incorrectly without redirect

🏗️ Build Differences

Edge Functions:

Edge Functions bundling
- render (12.6s)

Serverless Functions:

Functions bundling  
- sveltekit-render.mjs (5.5s)

🎬 Conclusions

  1. Neither solution properly implements prefix-all-locales routing behavior
  2. Edge functions are completely broken (500 errors)
  3. Serverless functions avoid crashes but don't redirect properly
  4. The issue is a deeper incompatibility between paraglide's routing strategy and Netlify's function runtime environments

💡 Recommendations

  1. Immediate workaround: Use serverless functions (edge: false) to avoid 500 errors, acknowledging this doesn't solve the fundamental routing issue
  2. Proper fix needed: Investigate why paraglide's routing logic fails in both Netlify runtime environments
  3. Alternative approaches:
    • Remove prerender filtering and let both prefixed/non-prefixed routes be prerendered
    • Implement custom redirect logic in netlify.toml
    • Test with other deployment platforms

This investigation demonstrates that the core problem is not simply "edge vs serverless" but rather a fundamental incompatibility between paraglide's prefix-all-locales routing strategy and Netlify's function runtime environments when combined with the current prerender filtering approach.

Summary: Created in /notes/summary/20250616T14.edge_vs_serverless_500_investigation.summary.md

@anthonybailey
Copy link
Collaborator Author

Yeah, I know it has jumped to conclusions a bit. But let's pat it on the head anyway, and I'll attach files.
20250616T14.edge_vs_serverless_500_investigation.txt
20250616T14.edge_vs_serverless_500_investigation.summary.md

What's that you say little buddy?

Essential Log Files (Must Attach)

  1. netlify-500-test.log - Shows edge functions build and 500 error reproduction
  2. netlify-serverless-serve.log - Shows serverless functions build and "working" behavior

Additional Valuable Log Files

  1. netlify-de.log - Shows baseline testing with German locale enabled
  2. netlify-prefix-all.log - Shows testing with prefix-all-locales strategy applied
  3. netlify-serverless-build.log - Shows the build process that timed out when switching to serverless

OK then.

netlify-serverless-serve.log
netlify-serverless-build.log
netlify-prefix-all.log
netlify-de.log
netlify-500-test.log

@anthonybailey
Copy link
Collaborator Author

I assume messing with the internal state by filtering in the adapter is just a busted idea. Will verify and look for proper alternatives.

@Wituareard
Copy link
Collaborator

Isn't the adapter with the filter commented out everywhere?

@anthonybailey
Copy link
Collaborator Author

🔍 Root Cause Identified: Adapter Patch Timing Issue

After investigating the SvelteKit source code, I've discovered why our adapter-patch-prerendered.js approach cannot work:

The Problem: Manifest Written Before Adapter Runs

SvelteKit Build Sequence (from packages/kit/src/exports/vite/index.js):

  1. Lines 1031-1038: Prerendering happens, populates prerendered.paths
  2. Lines 1041-1049: 🚨 Final server manifest written with prerendered.paths:
    fs.writeFileSync(
      `${out}/server/manifest.js`,
      `export const manifest = ${generate_manifest({
        build_data,
        prerendered: prerendered.paths,  // ← Uses original paths
        relative_path: '.',
        routes: manifest_data.routes.filter((route) => prerender_map.get(route.id) \!== true)
      })};\n`
    );
  3. Lines 1085-1094: Adapter runs - our patch modifies builder.prerendered.paths but it's too late

The Server Routing Logic

When a request comes in (e.g., /faq):

  • has_prerendered_path(manifest, "/faq") checks manifest._.prerendered_routes
  • If found, serves static file directly (bypassing middleware redirects)
  • If not found, goes through middleware where paraglideMiddleware can redirect

Since the manifest was written before our adapter patch ran, /faq is still in prerendered_routes, causing requests to bypass the middleware entirely.

Conclusion

The adapter patch approach is fundamentally incompatible with SvelteKit's build sequence. This explains why we see 0 redirects in our curl tests - the requests never reach the middleware.

@Wituareard
Copy link
Collaborator

Wituareard commented Jun 22, 2025

You just need to look at the logs to see that adapters run last. But the patch was only meant for the Netlify adapter so the paths are still passed to the function rather than served statically. It worked in the prototype and it's also commented out in all branches no? So yeah, pretty sure that's not it.

@Wituareard
Copy link
Collaborator

We should probably merge all the changes from main here right?

@anthonybailey
Copy link
Collaborator Author

anthonybailey commented Jun 22, 2025 via email

@Wituareard
Copy link
Collaborator

Wituareard commented Jun 23, 2025

The Netlify part isn't correct, the edge function handles all requests by default if they aren't explicitly excluded by the adapter. And as far as SvelteKit is concerned they are also still marked as pre-rendered (but not actually pre-rendered and written to the manifest bc nothing links there), we only tricked the Netlify adapter using the patching.

@Wituareard
Copy link
Collaborator

Edited the comment in case you only read the initial email

@anthonybailey
Copy link
Collaborator Author

anthonybailey commented Jun 23, 2025 via email

@anthonybailey
Copy link
Collaborator Author

anthonybailey commented Jun 23, 2025

Comment by Claude Code

Edge Function Timeout Investigation Results

Based on systematic testing of edge function timeout behavior using controlled delays with httpbin.

Test Environment

  • Local Netlify CLI (◈ Injecting environment variable values for all scopes
    ◈ Ignored general context env var: LANG (defined in process)
    ◈ Injected .env file env var: AIRTABLE_API_KEY
    ◈ Injected .env file env var: AIRTABLE_WRITE_API_KEY
    ◈ Injected .env file env var: OPENAI_KEY
    ◈ Injected .env file env var: ANTHROPIC_API_KEY_FOR_WRITE
    ◈ Injected .env file env var: PARAGLIDE_LOCALES
    ◈ Injected .env file env var: L10N_OPENROUTER_API_KEY
    ◈ Injected .env file env var: L10N_BRANCH
    ◈ Injected .env file env var: GITHUB_TOKEN
    ◈ Using simple static server because '[dev.framework]' was set to '#static'
    ◈ Running static server from "pauseai-website/build"
    ◈ Could not acquire required 'port': '37572')
  • All endpoints confirmed handled by edge function (verified via x-nf-edge-functions: render header)

Observed Results (in chronological order)

Endpoint Test Description Expected Time Actual Time Success Notes
simple-delay?delay=0 0s delay 0s 1.7s Cold start
simple-delay?delay=5 5s delay 5s 5.3s
simple-delay?delay=2 2s delay 2s 2.8s
simple-delay?delay=10 10s delay 10s 10.9s
simple-delay?delay=0 0s delay 0s 0.7s Warm
test-timeout?count=4&delay=10 4×10s sequential 40s 33.0s Failed on 4th request
test-timeout?count=3&delay=10 3×10s sequential 30s 31.7s
test-timeout?count=3&delay=10 3×10s sequential 30s 34.6s
test-timeout?count=7&delay=5 7×5s sequential 35s 48.7s HTTPBin 5s delay takes ~9s
test-timeout?count=8&delay=5 8×5s sequential 40s 52.7s
test-timeout?count=9&delay=5 9×5s sequential 45s 57.9s
test-timeout?count=10&delay=1 10×1s sequential 10s 24.3s
test-timeout?count=5&delay=10 5×10s sequential 50s 60.0s
test-timeout?count=4&delay=10 4×10s sequential 40s 45.1s Retry 1
test-timeout?count=4&delay=10 4×10s sequential 40s 36.4s Retry 2: Failed on 4th
test-timeout?count=4&delay=10 4×10s sequential 40s 22.9s Retry 3: Failed on 3rd

Key Observations

  1. I/O suspension confirmed: Edge functions successfully complete operations taking 60+ seconds, proving I/O wait time doesn't count against CPU budget

  2. Inconsistent timeout behavior: The same test (4×10s) produced different results:

    • First attempt: Failed at 33s
    • Retry 1: Succeeded at 45s
    • Retry 2: Failed at 36s
    • Retry 3: Failed at 23s
  3. HTTPBin timing variance: 5s delays actually take ~9s, 10s delays take 11-12s

  4. No clear timeout threshold: Operations succeeded up to 60s, but sometimes failed as early as 23s

Implications for prefix-all-locales Investigation

These results suggest:

  • Edge function timeouts are not deterministic in local Netlify CLI
  • Production behavior may differ significantly from local testing
  • The 500 errors with prefix-all-locales are unlikely to be simple timeout issues
  • Need to test in actual production environment for reliable results

@anthonybailey
Copy link
Collaborator Author

Yeah, you can see it's a real saga. This is finally getting nearer to something where all observations add up though.

Today, I had started using /api/write for some testing because this whole "wait, is it edge or serverless?" question was becoming relevant to Andrei's work on #372

Since I do have serverless redirects working, if we split things out properly I'm optimistic for prefix-all-locales, in an arrangement where we have much smaller edge functions and sensible sized serverless ones. It'll be so good.

Cross those fingers.

I've committed the test delay/timing functions and we'll verify in the production context next.


Netlify's docs starting here are actually really extensive. Lots of detail on how edge and serverless are designed to work together. Still haven't fully consumed them.

(And you can tell that Claude hasn't, or at least keeps forgetting things.) Claude Code struggles more than claude.ai stand-alone because of needing more context for the codebase. I got better information from the latter ending with asking it to write questions for its sibling. Weird world.)

@Wituareard
Copy link
Collaborator

Wituareard commented Jun 23, 2025

Serverless functions have a lot less free requests, not excited about this approach. And a combination is also not supported by any available SvelteKit adapter. How is it possible that nothing showed up in the logs for the 500s btw?

@Wituareard
Copy link
Collaborator

We should really try not to stray away too far from the default solutions imo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants