Nextjs module federation with standalone servers - next.js

I'm trying to use module federation (webpack 5) beetween nextjs applications.
I started from this example (two nextjs applications) and everything works as expected. From my point of view the problem is that this works only if i have both app on the same host.
The relevant webpack configuration part on next.config.js is below (the same in the other app)
....
remotes: {
next1: isServer
? path.resolve(
__dirname,
"../next1/.next/server/static/runtime/remoteEntry.js"
)
: "next1",
},
...
If i just remove the server configuration it doesn't works.
It is possible to use module federation between nextjs app without configure the remote server by folder path and reference the remote app only by url ?

It is possible, but it won't work with SSR. Just leave the remote with the global for client side:
// next.config.js
....
remotes: {
next1: "next1",
},
...
Create your component and import the remote:
// component.js
const Component = (await import("next1/component")).default
export default Component
And finally in your page, lazy load your remote component with SSR disabled:
// mypage.js
import dynamic from 'next/dynamic'
const RemoteComponent = dynamic(
() => import('../components/component.js'),
{ ssr: false }
)
const MyPage = () => (<RemoteComponent />)
export default MyPage
There's a working sample here: https://mf-shell.vercel.app/
And the code: https://github.com/schalela/mf-nextjs

Related

nextjs reload page with Link component

I have a simple nextjs 13 application (appDir) with 2 pages and a <Link/> component navigation.
first page Home - static
second page Test - receiving dynamic random content on the server side (fetch) from a third-party source.
Problem: When the application is rendered everything works fine, but when I try to switch between pages, my test page shows the old content, I can refresh the browser to get the actual data, is very similar to navigating through regular links <a/>, but i need without reloading the entire application.
Q: How can I force nextjs 13 to reload the Test page when I switch between pages with <Link/> component?
// src/components/navbar.tsx
'use client'
import {usePathname} from "next/navigation";
import Link from "next/link";
const navItems = [
{text: 'Home', href: '/'},
{text: 'Test', href: '/test'}
];
const Navbar = () => {
const pathname = usePathname();
return <nav className="nav nav-masthead justify-content-center float-md-end">
{navItems.map((item: { text: string, href: string, link?: boolean }, idx: number) => (
<Link key={idx} href={item.href} className={`nav-link${item.href === pathname ? ' active' : ''}`}>
{item.text}
</Link>
)
)}
</nav>
}
export default Navbar;
// src/app/test/page.tsx
import * as crypto from "crypto";
const getData = async () => {
const res = await fetch('http://localhost:3000/random-data', {cache: 'no-store'});
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
return <p>{crypto.createHash('sha256').update(JSON.stringify(await getData())).digest('hex')}</p>
};
I've recently asked about this same topic on their feedback discussion on github: https://github.com/vercel/next.js/discussions/41745?sort=new#discussioncomment-4620262
The cause of the problem is Link is only doing client side navigation and seems to serve a cached state of the previously visited component. You'll notice that the client never calls back to the server and thus the server component never runs the second time.
I've been searching for days, but haven't found a way to force Link to reload or force refresh the component to re-render.
My conclusion is that if you have dynamic data that needs to refreshed periodically, it's best to render it in a client component and not use a server component for now.
Also, if you'd like to use Suspense, you'll need to use a library like SWR or React Query for any client side data fetching.

Error: Minified React error #321 on Wordpress custom block plugin

Whenever I write a useEffect() inside a component function of my block plugin, the edit page goes blank and the console logs the message:
react_devtools_backend.js:4026 Error: Minified React error #321; visit https://reactjs.org/docs/error-decoder.html?invariant=321 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at Object.it (react-dom.min.js?ver=17.0.1:9:43163)
at e.useState (react.min.js?ver=17.0.1:9:10899)
at Prompt (Prompt.js:5:35)
at N (element.min.js?ver=3dfdc75a0abf30f057df44e9a39abe5b:2:9552)
at U (element.min.js?ver=3dfdc75a0abf30f057df44e9a39abe5b:2:10502)
at N (element.min.js?ver=3dfdc75a0abf30f057df44e9a39abe5b:2:9284)
at lr (blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:111294)
at blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:137935
at xn (blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:138073)
at blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:139086
The component:
import React, { useState, useEffect } from "react";
import axios from "axios";
function Prompt(props) {
const [data, setData] = useState({ hits: [] });
useEffect(() => {
const fetchData = async () => {
const result = await axios(
"http://my-site-test.local/wp-json/wp/v2/posts?_fields[]=title"
);
setData(result.data);
};
fetchData();
}, []);
console.log(data);
return (
<>
JSX...
</>
);
}
export default Prompt;
I tried to delete node_modules and reinstall to no avail…
I believe the problem is in my-plugin/src/index.js — wp.blocks.registerBlockType's 'save' property only allows static HTML to be returned (so it can be stored in the database within the content) and I was trying to insert a React component into it.
Since I want a dynamic block on the front-end, I have to load a register_block_type in my-plugin/index.php to render my component.
EDIT You actually can add React directly in the save attribute if you have specified script when registering your block in the PHP main file (or in your block.json file.

ASP.NET serving angular app and deployUrl is deprecated

I have an ASP.NET app (.NET 6.0), and I have a route /ngxapp which serves my angular 13 app. The angular app is copied to wwwroot/ngxapp folder (see below how it's built).
I've been using this .NET code for years to deliver angular app:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
// this is for reverse proxy (NGINX)
app.UseForwardedHeaders(new()
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
}
app.UseHttpsRedirection();
// this must be before the next (angular) section, otherwise 404's are not handled
app.UseStatusCodePagesWithReExecute($"{ANGULAR_APP_ROUTE_S}/404");
app.Use(async (context, next) =>
{
RewriteXFrameOptionsHeader(context);
await next();
RedirectEmptyRootToAngularApp(context, ANGULAR_APP_ROUTE_S);
await RewriteAssets(context, next);
await RedirectForAngular(context, next, ANGULAR_APP_ROUTE_S);
});
app.UseDefaultFiles(new DefaultFilesOptions {DefaultFileNames = new List<string> {"index.html"}});
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
});
It allows to open angular app even when user requests for some non-root angular route (for example, https://<domain>.com/ngxapp/someroute-1/subroute/etc). Basically, everything works like a charm.
Now, when I build an angular app I've always used --deploy-url=/ngxapp/ param, like so (from windows batch .cmd file):
call ng build --base-href /%folder% --deploy-url /%folder%/ --configuration=production
The latest version of angular compiler shows me the warning about deployUrl:
Option "deployUrl" is deprecated: Use "baseHref" option, "APP_BASE_HREF" DI token or a combination of both instead. For more information, see https://angular.io/guide/deployment#the-deploy-url.
I read the docs on APP_BASE_HREF and built angular app without --deploy-url parameter. Also, used APP_BASE_HREF:
#NgModule({
declarations: [AppComponent, CreateUrlComponent, NotAuthorizedComponent, FormFocusDirective],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
...
],
providers: [
{ provide: APP_BASE_HREF, useValue: environment.baseHref }, // baseHref = "/ngxapp"
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule {
constructor() {}
}
But now, when I run ASP.NET app with the angular app in place (opening https://localhost/ngxapp in the browser), the requests of all .js files are empty and their MIME type is text/html. Basically, they're not found.
If I return back to using deploy-url parameter in the angular build, everything works!
What am I missing here?
Comes down to this
When --deploy-url is used, the request Url is correct:
https://localhost:44389/ngxapp/filename.js
because the script block contains correct url's:
<script src="/ngxapp/runtime.js" type="module"></script>
When --deploy-url is not used, ngxapp part is missing:
https://localhost:44389/filename.js
and the script block is:
<script src="runtime.js" type="module"></script>
I made it work. Here's how I wanted the angular part to work within ASP.NET app:
if I have a special route for angular app called /ngxapp within my ASP.NET application, I want any link containing '/ngxapp' part to work seemlessly. For example, if a user paste https://example.com/ngxapp/child/subchild to a browser address, and the angular app does have child/subchild routes, then ASP.NET app should serve the https://example.com/ngxapp/index.html and the angular router takes over and serves child/subchild.
The problem is when ASP.NET serves angular's app index.html from /ngxapp, the script tags in the index file must have a valid path related to wwwroot/ngxapp/ folder (scr='/ngxapp/some-angular-file.js, and not just src='/some-angular-file.js'). This works when --deploy-url flag is used.
Now, when the flag is deprecated, I had to create a rewrite rule in ASP.NET which actually catches any file served from angular app folder and rewrites it to the correct path accordingly: from /whatever-angular-file.js to /ngxapp/whatever-angular-file.js.
With this custom URL rewriting rule in place, everything works as expected, and --deploy-url is not needed.

NextJS and Keycloak integration (Opt-out of Automatic Static Optimization issue)

I am trying to integrate keycloak with NextJS.
The library that I am using is: https://www.npmjs.com/package/#react-keycloak/ssr
In documentation they said to wrap SSRKeycloakProvider component over the App component in _app.tsx file.
Like this:
function MyApp({ Component, pageProps, cookies }: AppProps & InitialProps) {
return (
<SSRKeycloakProvider
keycloakConfig={keycloakCfg}
persistor={SSRCookies(cookies)}
>
<Component {...pageProps} />
</SSRKeycloakProvider>
)
}
...
MyApp.getInitialProps = async (context: AppContext) => {
// Extract cookies from AppContext
return {
cookies: parseCookies(context?.ctx?.req),
};
};
I have tested it and everything works fine, until I pushed my changes to gitlab and pipeline failed with error message:
Warning: You have opted-out of Automatic Static Optimization due to
getInitialProps in pages/_app. This does not opt-out pages with
getStaticProps Read more:
https://nextjs.org/docs/messages/opt-out-auto-static-optimization
Seems that this will cause that all pages without getStaticProps() method will be server side rendered. How to avoid that?
Should I create one more component which will look similiar like this MyApp(), and wrap it only over the pages that require keycloak authentification? (and then remove that SSRKeycloakProvider from MyApp and at this to that new one)

Next.js Amplify Serverless AppSync graphql (Amplify error)

I'm trying to create an App that uses Amplify and AppSync with Next.js.
I was able to deploy the application successfully but when I'm trying to access the application (trying to fetch data from AppSync) It is failing.
I'm kinda lost what needs to be looked at, the application seems to be working fine locally but when I'm publishing it on AWS it is failing.
I'm getting error: TypeError: Cannot read property 'getName' of undefined
//quotes.js
import { Connect } from 'aws-amplify-react';
........
<Connect
query={graphqlOperation(queries.getName)}
subscription={graphqlOperation(subscriptions.onCreateName)}
onSubscriptionMsg={(prev, { onCreateName }) => ({
...prev,
getName: {
...prev.getName,
items: [
...prev.getName.items,
onCreateName,
],
},
})}
>
Has anybody come across such an issue or any idea to resolve the issue? Thanks in advance.
Running the subscription when doing SSR is not needed / useful and requires a WebSocket client in the server, you can try Dynamic Imports with no SSR to have the subscription only happen on the client. (using { ssr: false })
import dynamic from 'next/dynamic';
const DynamicComponentWithNoSSR = dynamic(() => import('../components/hello3'), { ssr: false });
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
);
}
export default Home;

Resources