Hacker Newsnew | past | comments | ask | show | jobs | submit | teekoiv's commentslogin



It's baffling to me why more SSR frameworks, Astro and NextJS namely, can't adopt static pages with dynamic paths like SvelteKit. So for example, if you have a page /todos/[todoId] you can't serve those in your static bundle and NextJS straight-out refuse building your app statically.

Whereas with SvelteKit, it builds happily and does this beautiful catch-all mechanism where a default response page, say 404.html in Cloudflare, fetches the correct page and from user-perspective works flawlessly. Even though behind the scenes the response was 404 (since that dynamic page was never really compiled). Really nice especially when bundling your app as a webview for mobile.


I partly agree with you, but it is a design decision that comes with a drawback. A URL /todos/123 cannot be resolved in a SPA in a hard-reload. I.e. if a user were to bookmark /todos/123 or press reload in the browser, the browser would ultimately ask the underlying HTTP server for that file. As you mentioned, you would need a 404 page configured to fetch that - but that requires a configuration in the HTTP server (nginx etc.). So you are not just a static html+js+css+images deploy, you always will need server support. Another issue is, that 4xx errors in the HTTP spec are treated differently than 2xx: most notably, browsers are NOT allowed to cache any 404 responses, no matter what response header your server sends. This will ultimately mean, those /todo/123 bookmarks/hard-reloads will always trigger a full download of the page, even though it would be in the cache. And again, you would always need support in the web server to overwrite 404 pages. While the current NextJS output can be just deployed to something like github-pages or other webspace solutions.

Now, these are just the limitations I can think of, but there are probably more. And to be fair, why "break" the web this way, if you can just use query params: /todo?id=123. This solves all the quirks of the above solution, and is exactly what any server-side app (without JS) would look like, such as PHP etc.


> if a user were to bookmark /todos/123 or press reload in the browser, the browser would ultimately ask the underlying HTTP server for that file. As you mentioned, you would need a 404 page configured to fetch that - but that requires a configuration in the HTTP server (nginx etc.). So you are not just a static html+js+css+images deploy, you always will need server support.

> use query params: /todo?id=123. This solves all the quirks of the above solution, and is exactly what any server-side app (without JS) would look like, such as PHP etc.

We had PATH_INFO in virtually every http server since CGI/1.0 and were using it for embedding parameters in urls since SEO was a thing, if not earlier. Using PATH_INFO in a PHP script to access an ID was pretty common, even if it wasn't the default.

By way of example, here's a sample url from vBulletin, a classic PHP application <https:/ /forum.vbulletin.com/forum/vbulletin-sales-and-feedback/vbulletin-pre-sales-questions/4387853-vbulletin-system-requirements>[0] where the section, subsection, topic id, and topic are embedded into the URL path, not the query string.

[0] https://forum.vbulletin.com/forum/vbulletin-sales-and-feedba...


Interesting. You can set up the server to respond with 200.html as the catch-all so the requests would return 200. There was some issue with it—can't remember what—which is why I switched to 404.html. After the initial load though the subsequent navigations would go through pushState so I think they'd be cached.

But I don't see this is as big of a problem. With this I can switch and choose—SSR dynamic pages or use hacky catch-all mechanism. For any reasonably large site you probably would SSR for SEO and other purposes. But for completely offline apps I have to do zero extra work to render them as is.

Personally, I much prefer route paths vs query parameters not just because they look ugly but because they lose hierarchy. Also, you can't then just decide to SSR the pages individually as they're now permanently fixed to same path.


I tend to agree that you could come up with a solution using server-side catch-all and custom 200/404 routes - and actually I do, as I use nginx with a single line of try_files customization. But this is optional. It shouldn't be required to mess with the server config, if you want a static deployment.

Besides, if you catch-all to a 200.html page, how would you serve 404s? Yes, you can integrate a piece of JS in the 200.html file and have it "display" 404, but the original HTTP response would have been 200 (not 404). A lot of bending web standards and technology, and I can see how framework authors probably decide against that. Especially given how much shit JS frameworks get for "reinventing the wheel" :)


It doesn't really matter from user's point of view if the response is 200 or 404 if the end result is the same. This is just a rendered web page after all. But yeah, you can get stuck in the semantics of it but I personally just use what works and move along.


Maybe i misunderstood you, but I did dynamic routes/pages for Next and Astro static builds. Using contentful or storyblok as a CMS, where the editor defines the routes and the components/bloks per page. Basically, the projects had one slug like [...slug].

Routes and Components per Page are dynamically created while export Next or build Astro static pages. In both frameworks you create the pages / slugs via getStaticPaths. And if ISR/ISP is enabled, even new pages (that are not known during build time) are pre-rendert while running the server.

In Next it is called dynamic routes[1] and in Astro dynamic pages[2]. Catch all slugs in Next and Astro are [...slug] e.g..

[1] https://nextjs.org/docs/pages/building-your-application/rout...

[2] https://docs.astro.build/en/guides/routing/#example-dynamic-...


Maybe I am misunderstanding you, but isn't this what Astro's `getStaticPaths`[0] function is for?

[0]: `https://docs.astro.build/en/guides/routing/#static-ssg-mode


I think since they used [todoId] in the example they mean a static page which does not exist at build time. Which both can do, it’s called ISG (or on-demand in the Astro docs), but it requires a server to work, or you can create a static route that accepts any parameters and pass those to JavaScript to run on the client.


Yeah, what the other commenter said. getStaticPaths still requires you to define the rendered routes build-time



That's cool but I fail to see how this results in 100% static bundle.


afaik astro will serve a live collection as a static HTML file (unless you explicitly include JS)


I’m also a big fan of static, and nextjs supports this: https://nextjs.org/docs/app/api-reference/functions/generate...


It doesn't. Those are executed build-time and you can't just set a wildcard so anything outside the given set results in 404.

As background, I wanted to make a PoC with NextJS bundled into a static CapacitorJS app somewhat recently and had to give up because of this.

You can try tricking NextJS by transforming the pages into "normal" ones with eg query parameters instead of path, but then you need complicated logic changing the pages as well as rewriting links. As you of course want the normal routes in web app. Just a huge PITA.


Not sure why you gave up. All you need to do, is use query params: /todo?id=123 and use `const { id } = useSearchParams()` in your code. Yes, the urls won't be that pretty, but I don't know if this is a road block. I have a NextJS webapp up and running that is 100% SPA (no SSR, full static export) and uses a C#/.NET REST Backend for data [0]. Works (almost) flawlessly.

[0]: lockmeout.online


No. I disagree, you have to refactor all the pages from using [pathId] folder pattern and change all the links including switching to useSearchParams. It's just a huge change, especially if I want to keep the old routes in my web app.


Yes it would, and I can see with an existing app that it is work. But we are probably talking less than a days work? Not even using AI, but this is straightforward string replacements.


Right. So a custom build system which changes the built pages from dynamic path folders to named folders in a day. No bugs. Sure. Also I serve rich-text content with links so those gotta be rewritten as well.


I might not be understanding you (or the various frameworks) completely, but are Astro's server Islands what you're looking for?

https://docs.astro.build/en/guides/server-islands/



You can indeed do that with Next.js. In the app router, it’s called generateStaticParams. In the pages router, it’s getStaticProps.


This isn't the same as getStaticProps is evaluated at build time not runtime


I very much agree with the overall thesis of the post. Runes are just irksome in subtle ways.

One big point that the post misses, is that the Class escape hatch for runes is incompatible with constructor-set parameters.

Say you have a class that wraps a HTMLElement which you set in the constructor. This doesn't work:

  class Wrapper {
    dom: HTMLElement = $state()
    constructor(el: HTMLElement) {
      this.dom = el
    }
  }
as TypeScript throws an error about `Type 'undefined' is not assignable to type 'HTMLElement' for the $state()`. You could fix it by eg. `$state(undefined as unknown as HTMLElement)` but that's dumb. Interesting enough you could do something like:

  class Wrapper {
    dom: HTMLElement
    constructor(el: HTMLElement) {
      let d = $state(el)
      this.dom = d
    }
  }
Moreover, Vite/esbuild mangles class-field parameters with esnext into constructor-set parameters as they are just more versatile. So the original code becomes something like:

  class Wrapper {
    constructor(el: HTMLElement) {
      this.dom = $state(el)
    }
  }
Which is incompatible with rules of runes. I did whine about this already https://github.com/sveltejs/svelte/issues/14600 but so far no clear answer


I agree with everything in the post but I still like using Svelte. We've adopted our own Store class and decorators which eliminates some of the issues in the post.

    class Wrapper extends Store {
        @state() accessor dom: HTMLElement;
        constructor(el: HTMLElement) { this.dom = el; }
    }
The advantages here are that a) we don't need the .svelte.ts postfix and b) @state() makes TS support flawless with runes. And since we only use classes for state, most of the other objections in the post are mitigated. On the bindable issue, we simply just don't use that feature - one way dataflow ftw! :)

I'm not saying Svelte 5 is flawless at all, but I think we found an approach that minimizes the downsides. The upside is really good performance and a metaframework that makes the most sense out of all the current options.


Oh wow, so this still works even though Svelte doesn't support TypeScript features that aren't just type annotations (like decorators)? Is that because you're outside of `svelte.ts` files so the compiler never touches them?


Yeah it works fine with the latest sveltekit. You do need to set { esbuild: target: "es2022" } in the vite config to enable JS decorators but that isn't specific to svelte. These aren't TS decorators, they're ecmascript decorators (https://github.com/tc39/proposal-decorators) which are at stage 3 and being adopted by JS runtimes.

Under the hood the class is just creating a hidden property with $state({}) and the accessors are reading/writing to that property. Since $state() creates a deeply reactive object it acts the same as marking individual fields with $state().


> You could fix it by eg. `$state(undefined as unknown as HTMLElement)` but that's dumb

I think the generic is what you are looking for

  class Wrapper {
    dom = $state<HTMLElement>();
    constructor(el: HTMLElement) {
      this.dom = el;
    }
  }
the dom property will still be HTMLElement | undefined, if the 'undefined' bothers you have to add an exclamation mark and write "$state<HTMLElement>()!"


That's still a manual type assertion though, and if a regular usage pattern demands one, then the library is doing it wrong. Regardless of how you annotate it, every manual override reduces the effectiveness of the type system.


Use $state()! to fix the undefined is not assignable error.


Runes annoy me because they ruined the use of Svelte components as rendered widgets outside a Svelte site. I like using them in rich-text as rendered views but when Svelte 5 came out, the old way of simply setting the component props directly was thrown out and you implicitly are now expected to use runes for the props you pass to the components.

Which means, that anywhere I render those components I have to suffix the file as .svelte.ts Or switch to svelte/store but it's quite dumb to wrap everything as a Writable<T>

I still like Svelte regardless, but I just thought "why" as over-abstraction wasn't the original tenets of Svelte


Yeah, I opted not to talk about that in my post, but runes do tend to infect all of your code if you use them as intended.


Well I'd say this is rather clumsy attempt with no real upside on my side. 10% of imaginary profits for giving access to my computer, IP, Upwork—not even taking into consideration the illegality—with zero guarantees I'll see a single cent.


Great job on launching! It sure does feel good to ship something and get a positive reaction. I don't want to steal your thunder but I did something similar a year ago and posted it here with a fresh HN account without much fanfare.

https://midi-note-trainer.teemukoivisto.xyz/

I maybe put even too much effort in it, all those different keyboard layouts and all. But I am happy with the end result and noticed it did actually work. For me, the most important thing was (and still is) just learning music notation. Maybe one day I'll make a v2.


i was not able to change the midi device to the appropriate one, so i couldn't use it, it looks really good though.


+1, even when I unplug everything beside my keyboard and remove any virtual devices, it stumbles onto "unknown input port" before my keyboard. I'd like to try it


Damn. I did use webmidi to wrap the access but apparently there's something missing. Works with my MPK3. Thanks for the feedback.


  Location: Helsinki
  Remote: Yes
  Willing to relocate: No
  Technologies: JS/TS, HTML/CSS, NodeJS, Svelte, React, Vue, ProseMirror, Tailwind, AWS, Terraform, Postgres
  Résumé/CV:  https://teemukoivisto.xyz/cv
  Email: [email protected]
I'm a full-stack dev of 6 years who is interested working on some Svelte or rich-text related project or anything else in the realm of TS with some infra or databases sprinkled on top.


  Location: Helsinki
  Remote: Yes
  Willing to relocate: No
  Technologies: Rust, TS/JS, NodeJS, Svelte, React, Terraform, Ansible, ProseMirror, HTML/CSS, Tailwind
  Résumé/CV: https://teemukoivisto.xyz/cv
  Email: [email protected]
I have 6 years of work experience with webdev and rich-text but I've come interested in learning Rust. During the past 1.5 years I've taught myself the basics and programmed a game engine (with a friend) in Rust with multiplayer (Rapier, tokio, axum, Flutter) as well as websocket server for collaborative editing.

I'd consider myself moderately skilled in getting stuff done with Rust but there's still a lot to learn about the intricacies of memory management and general best practises. So I'm looking for opportunities where I could write Rust with perhaps some webdev on the side.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: