nextjs params not available during build - javascript

I'm using nextjs 13 with experimental app directory. My app uses dynamic routing like so:
https://app.domain/[itemid]/[slug]
In my [slug] directory, if I create pages.jsx, a server site route, with the following:
export default async function pageComponent ({ params, ...props }) {
console.log('params': params);
const data = await afunction(params.itemid);
...
The app works as expected when I run as dev. A url such as:
https://app.domain/44/pagetitle
Outputs params.itemid = '44' and params.slug = 'pagetitle' in the console, as I expected.
Problem
I can't build the app for production. If I run npm run build, it attempts to compile page.jsx and finds that afunction on pageComponent is undefined as it had no params.itemid. A user never targeted a specific url, so no params were generated when it's building. The params come from dynamic values set in a growing database. The various values in the database may be edited so I don't want an entirely static page.
Research
Based on the documentation, I'm unsure of how else I'm supposed to handle this. I feel I'm missing a step here but I don't feel I need to generateStaticParams but perhaps I'm wrong on this one?

In my situation, the solution was to add a generateStaticParams function, after all. There is a light reference to it in the documentation, under dynamic segments, which I read as an optional preference. Although I wasn't interested (yet) in having static pages created at build time, I was unable to get params working at all without using it, once building and using server side components.
For my use case, I attempted to create a single static page, using it:
export async function generateStaticParams() {
const staticParams = [
{
itemid: '4214',
slug: 'apagetitle'
}
]
return staticParams;
}
Which allowed the app to build successfully. Note, this was just for testing purposes and trying to better understand the issue. From the examples, this is intended to generate static pages for entire, or large sections of a database and the examples in the documentation illustrate this fine.

Related

"Text Slot Machine" to be hosted on the web using nginx. Issue with npm

I'm developing a full stack application that consists of two systems, one with a printer and one hosted remotely handling a web-based "slot-machine" style app that provides a string to be printed on the other system.
I already created the communication and printer connection components based on python. I read on the subject of the web part and realised that the best would likely be to use nginx to serve the pages to the visitors. Initially I had considered directly using django (instead of flask), but because there might be a bit too much of concurrency and updating the page from the server side might cause slowdowns or even crash it, I believe that some sort of javascript based implementation would be best. Most of my experience is outside of javascript so there's quite the barrier here, nevertheless I found vue.js to appear quite intuitive. I found a slot-machine that would match 50% of my requirements, namely the first example in it's description.
What I want to do on the webpage is simple: the slot machine rolls on page load, the person presses a button and it stops. The position where it stops is sent to the backend and the subsequent processing does not seem to be out of reach for me. Unlike the webpage at this point.
Can be skipped to the second edit
I created a npm environment on my system, installed vue and installed the package, as per the readme. However, independently of how and where I try to load it, vue is never able to find it.
I tried adding to the body of index.html:
<script src="https://raw.githubusercontent.com/josex2r/jQuery-SlotMachine/master/dist/jquery.slotmachine.js"></script>
Inside app.vue, I tried loading it inside the script part in all these different manners:
var SlotMachine = require('jquery_slotmachine.js');
import SlotMachine from "jquery-slotmachine"
import SlotMachine from './components/jquery.slotmachine.js'
import SlotMachine from '#/components/jquery.slotmachine.js'
import * as SlotMachine from '../src/components/jquery.slotmachine.js'
import SlotMachine from "jquery_slotmachine.js"
None of which resulting in anything. Not even on the export component:
export default {
name: 'App',
components: {
HelloWorld
},mounted() {
const plugin = document.createElement("script");
plugin.setAttribute(
"src",
"#/components/jquery.slotmachine.js"
);
plugin.async = true;
document.head.appendChild(plugin);
}
};
Any idea/help?
Thank you in advance!
#Edit:
<script src="https://raw.githubusercontent.com/josex2r/jQuery-SlotMachine/master/dist/jquery.slotmachine.js"></script>
Typo when writing on stackoverflow. Issue still applies.
#Edit:
So I discovered rawgithub was not providing the data, or better, was providing an empty file (header issue?), anyway, I hosted the file online (private cloud) and trying again with the new source I got an issue in a return. I realised that there might be an issue with this build so I went back to a older one that had success when running the tests. I downloaded all these lib files, placed them in the components folder and using "import SlotMachine from './components/slotmachine.js" was able to get the file to appear in the network tab of the browser console.
With this out of the way, my new issue is related to this line in the begining of the class, which was detected via the browser console.
class SlotMachine {
constructor (element, options) {
this.element = element;
// Slot Machine elements
this.tiles = [].slice.call(this.element.children);
My page has the following script:
<script>
import SlotMachine from './components/slotmachine.js'
export default {
name: 'App',
components: {
}
};
const el = document.querySelector('#machine');
const machine = new SlotMachine(el, {active: 1,delay: 450,auto: 1500});
</script>
In which the #machine relates to:
<template>
<div id="app">
<div id="machine">
<div>Madrid</div>
<div>London</div>
<div>New York</div>
</div>
</div>
</template>
Similarly to the example. However, with this the element #machine is not passed to the class leading then to an error on the this.tiles[] element, since element is None
Is the document not fully loaded thus the querySelector cannot find the element?
Thank you in advance!
Change your script tag from this:
<script> src="https://raw.githubusercontent.com/josex2r/jQuery-SlotMachine/master/dist/jquery.slotmachine.js"></script>
to this:
<script src="https://raw.githubusercontent.com/josex2r/jQuery-SlotMachine/master/dist/jquery.slotmachine.js"></script>

Next.js dynamic routes using a different URL

I am currently updating a React project to use Next.js. I've stumbled on a slight issue with Dynamic Routing which so far I can't find any solutions online.
I have a number of info pages that use the same component and display different content based on the slug. Using react-router these can be specified as follows:
path: /:infoPage(about-us|terms|privacy|contact|faqs)
/about-us, /terms, /privacy, /contact, /faqs
So far for Next.js, I thought I'd use a dynamic route as seen below. The only issue is that the URLs will now have /infoPage/___ - /infoPage/about-us, /infoPage/terms.
/pages
/infoPage
/[infoPage].js
As a solution links can be updated using as with the proper URL:
<Link href="/infoPage/[infoPage]?infoPage=about-us" as="/about-us">
<a>About Us</a>
</Link>
Whilst this works when clicking on a link, refreshing the page will end up a 404 page - since Next.js is not aware of any page as /about-us.
Possible Solutions
Use dynamic route /pages/[infoPages].js. Not quite sure if this is the ideal solution as it would most probably act like a fallback to any other page.
I suppose I could have every page declared separately within /pages and import the same component in each page but it will be slightly repetitive.
/pages
/about-us.js
/terms.js
...
Keep /infoPage/[infoPage].js route, add redirects from client/server to the proper URL
I may be over thinking this, the second solution would not be the end of the world but I'm still wondering if there's a better solution.
Thanks in advance :)
In case someone is looking for an answer to this question, here are two solutions you may consider.
1. Dynamic Route in /pages at root level
Use Case: Pages using the same components with similar data (for example API call to get page content)
If you have certain pages similar to each other at root level, you can create a dynamic route such as below:
/pages
/[rootPage].js
Keep in mind that if these pages are using Static Site Generation, slugs need to declared using getStaticPaths.
Further details on getStaticPaths here
It is also important to note that predefined routes take precedence over dynamic routes. Should a dynamic route have a slug the same as a predefined route, Next will always render the predefined route. So this approach can be risky at root level.
Dynamic Routing Caveats
2. Separate routes, one getStaticProps function
Use Case: Pages require similar data/API calls but use different components
You may opt to have routes for each page but use the same getStaticProps function.
Routes:
/pages
/about-us.js
/terms.js
getStaticProps helper function:
const getPageStaticProps = () => {
return async (context) => {
// generic logic here
}
}
Page (about-us.js, terms.js):
// /pages/about-us.js
function AboutPage(props) {
return <AboutComponent {...props} />
}
export const getStaticProps = getPageStaticProps()
---
// /pages/terms.js
function TermsPage(props) {
return <TermsComponent {...props} />
}
export const getStaticProps = getPageStaticProps()
This can also be implemented using getServerSideProps.
I opted for the second solution as it felt safer and still benefitted from having code shared between different pages. Obviously there may be other solutions our there that can be used :)

How to Setup Local Store with Static Data in Relay Modern

I'm building a React app that fetches data from a server and also passes static data to various components based on a user's selection from a dropdown menu. I'd like to use Relay Modern, but I've been unable to find anything in the docs about whether it supports manually loading static data to the store. Anyone know if this is possible/how to implement?
btw, I've seen a few similar questions about this here and elsewhere. But, it appeared that those pertained to Relay Classic rather than Relay Modern, which implemented massive changes.
I have have asked myself the same question. There must be a way to hydrate the Store/RecordSource. As a workaround I have been doing something like this. I create a query for the data that I want to add to the store and call commitPayload.
import {
createOperationSelector,
getOperation
} from 'relay-runtime';
const query = graphql `{ viewer { name } }`;
const environment = new Environment(...);
environment.commitPayload(createOperationSelector(getOperation(query)), {
viewer: {
name: 'Me'
}
});
Wondering if anyone from the relay team has some insight?

Is it possible to reload the router in a running ember.js app in order to update the path strings of routes?

I'm implementing multi-language support in my app, and I guess this is the last thing that I would need in order to be able to change between languages without reloading the whole app/page. (I already have a solution with full page reload.)
For a simple example let's say this is how my router looks:
Router.map(function() {
this.route('search', { path: t('search') });
this.route('item', { path: `${t('item')}/:id`);
});
The t function would be getting the correct translation for the given strings in the currently active language.
The structure of the route hierarchy won't change, the only things that need to be updated are the path strings. Application state should be kept, of course.
I'm wondering whether this is possible to do.
I am not %100 sure about the correctness of what I wrote but Router.map is executed and resources with the definitions given within this method is transformed to a DSL instance and that is then passed to the actual router maintained by Ember.Router itself. In order to achieve what you want I believe what we need is dynamic modification to the router even if it is just the paths you need yo modify not the whole route structure.
If you look at the following github issue, Ember.js no more supports dynamically adding routes (hence no dynamic modification to the existing ones I believe). With all that said, I believe what you want is not possible without reloading the whole app (hence losing the application state).

Can I send a React component to the client while doing Server Side Rendering?

I have a Reactjs application built on a Node/Express server. I am doing server side rendering via something like:
route.js
var Group = require('./react/component/groups');
var props = {foo: 'bar'};
var groupHtml = React.renderToString(Group(props));
res.render('index.ejs', {
reactOutput: groupHtml,
jsonProps: JSON.stringify(props),
start: 'lessonlist'
});
index.ejs
<div id="react-main-mount"><%- reactOutput %></div>
<script id="props" type="application/json"><%- jsonProps %></script>
<script src="/js/<%= start %>.js"></script>
This works really great! But here is my problem:
One of my pages/components will have a child component. But this child component can be one of 50+ different components.
I assume I could Require every single component, even though only one will be used. This seems like a bad idea since my main.js file will be huge.
I tried sending it via a prop and JSON.stringify. This works on the server rendering to HTML, but this doesn't work on the client side because JSON.stringify can't stringify a react component.
I though I might be able to do an API call and return it, but same problem as above.
Is there any way to dynamically require something, say based off a variable like require('components/' + this.props.foo) after the I've browserified/reactified?
Any suggestions/answers would be greatly appreciated
Browserify had always been too bulky for me, so I did my research and found RequireJS. It allowed me to require other files on the client just like how Node does it.
It was difficult for me to get started with Require, but once I got it working, everything just seemed easier. See this example I wrote in CoffeeScript.
Remember to have below code at the top of every isomorphic component so you don't stumble upon the problem I had with Node not understanding the keyword define.
if typeof define != 'function'
define = require('amdefine')(module)
define (require) ->
Above code is written in coffeescript.
Let me know if more information is needed.
Webpack allows you to do asynchronous bundle loading. This is intended for single page applications, where you don't want to have to include all of your javascript files until the user actually navigates to the "page" where they're needed. This allows you to fetch javascript with ajax dynamically. Check out the section titled 9. Async loading in this wonderful guide to Webpack
The implementation looks something like this...
if (window.location.pathname === '/feed') {
showLoadingState();
require.ensure([], function() { // this syntax is weird but it works
hideLoadingState();
require('./feed').show(); // when this function is called, the module is guaranteed to be synchronously available.
});
} else if (window.location.pathname === '/profile') {
showLoadingState();
require.ensure([], function() {
hideLoadingState();
require('./profile').show();
});
}
If you don't want to use Webpack, you could potentially just fetch the components with ajax as plain text and eval it upon arrival. This is essentially what the browser is doing with script tags under the hood.

Categories