Ok, I dug a bit into Next.JS. It seems it’s a client-side first framework, so your logic would all live in the client as an SPA, and the server-side part is pretty basic, meant to run as serverless functions in the cloud that would internally call some other APIs doing the real backend work, or the DB. But it handles nothing of backend data modeling and all that it seems. And it also is explicitly designed for pre-rendering first page load for faster initial loading and SEO.
They seem to let you create new “pages” by watching files in a folder.
Each page is associated with a route based on its file name
I guess they’re going the code-gen route here. Some command probably runs to “build” the app at the end, checks this folder, for every file, generates code and uses the file-name as the route name implicitly.
I guess this is an alternate code-gen approach, more like a post-processor, you don’t run it initially to scaffold, so instead of running a command to add a “page” where it creates the file in the folder for you, you create the file in the folder and after you run a command that will use this file to create the real page/route out of.
Interestingly, they use the filename as code, like you can use a DSL as your filename to create some code.
For example, if you create a file called
pages/posts/[id].js , then it will be accessible at
posts/2 , etc.
So they transform the filename here into a dynamic route. Making the name of files in a project into code is also something I would ask myself: madness or genius?
Is this even a valid filename on all OS ?
For the rest, it looks like you have to explicitly require whatever you need to use from inside the page. And to “plugin” to the framework, so it can call you, it is done by convention it looks like, so you need to have functions of specific names that the framework will call.
It also seems like it inspects what of those “by convention” functions you have used, and depending if it sees certain of them, that customizes the behavior. So if you implemented
getServerSideProps it’ll automatically assume you want to do per-page load SSR instead of build time SSR. That’s also an interesting trick.
It also looks like it uses the filesystem to override some of the framework behavior itself. Like if you want to replace how it loads a page, you create a _app.js file in some specific place, and it will use that module instead of the default one instead. Similarly, it just expects that you meet by convention the interface for it, so have the right functions that other things are meant to call.
Something else they do, which might be a JS thing, is that some functions you expose (using the convention), which it will call, those functions are passed as input a set of helper functions. So instead of requiring helper functions, you are provided them as input. Can you do that in Clojure? Can you pass a namespace to a function and use it?
And it seems that config is also handled by convention, like in the pages themselves, if you have a constant var with some config map, it uses that to configure certain aspect of itself.
Alright, so I think the interesting patterns here are:
Post-processing, like your code is not the complete app, you have to run a command and from it, it will generate the final result, and that’s where some of the customization and override/config logic gets applied.
File based extension points and overrides. You don’t override things or extend thing “in-language”, it’s all based around files in the project, almost like source code modules were config files with a set of rules to look them up and choose which one to use.