Is it possible to work hide a folder from a full slug of a page with Nextjs SSG and Storyblok - next.js

Due to the structure of my Storyblok Space, I'm unable to rewrite the final slugs to match my desired URL structure.
Inside Storyblok, I have to use the folder level translation and due to business rules, inside the language folders, I have to group stories inside "virtual folders".
The structure inside my root folder and the desired final paths are:
en/website/home - en/home
en/website/about - en/about
en/website/category/business - en/category/business
en/landing-page/example-lp - en/example-lp
es/website/home - es/home
es/website/sobre - es/sobre
In this cases, website and langing-page are "virtual folders".
Using NextJS SSG, I am able to use getStaticPaths and getStaticProps with the Storyblok API and generate the pages with the full slug. This is achieved with a [[...slug]].js file inside my pages folder.
If I work with only one virtual path, for example website/, I can just return the getStaticPaths params as follows:
[{
params: { slug: ['about'] }, // without the 'website' value
locale: 'en',
}]
Just appending the virtual folder on the API request, inside getStaticProps, generates the files according to the desired path.
let { data } = await Storyblok.get(`cdn/stories/${params.locale}/website/${params.slug}`, option)
The drawback of this approach is that I must use only one virtual folder. I could not create structure where I could send dynamically the virtual folder parameter separately so it could be appended on the Storyblok request URL.
What I tried so far:
I was able to use the rewrites key inside my next.config.js to dynamically hide the vitrual folders. However, it does not affect my server side generated files.
Use Storyblok Advanced paths app to return the real_path of the stories on the cdn/links/ request. And even though I can setup the correct values to the real_path parameter, without the virtual folder reference I cannot request the stories correctly inside getStaticProps.
I wasn't able to modify the slug values of the responses from the Storyblok API.
I wasn't able to return an additional parameter to the getStaticPaths method. For example:
[{
params: { slug: ['about'], folder: ['website'] },
locale: 'en',
}]
Or even:
[{
params: { slug: ['about'] },
locale: 'en',
folder: 'website'
}]
Obs: The object above kinda works if I change my project's folder structure to pages/[folder]/[...slug].js. However, even though I can access the folder parameter inside getStaticProps, it also goes to the final paths
Any suggestion, or different approaches to solve it is very welcome.

Related

Problems mounting a Vercel/Nextjs project as a subdirectory of a different Vercel/Nextjs project

I have a monorepo with two projects - web and docs. Each of these is their own Vercel project with the web project mounted at https://example.com and docs mounted at https://docs.example.com. All of this works as expected.
I now want to have the docs project be available at https://example.com/docs. In the web project, I setup the following rewrites in my vercel.json file.
{
"rewrites": [
{
"source": "/docs/:match*",
"destination": "https://docs.example.com/:match*"
},
{ "source": "/(.*)", "destination": "/" }
]
}
This works for the main index file, but all of the corresponding css and js files result in 404's. The browser is looking for those files at https://example.com/_next which isn't correct, it should be looking at https://docs.example.com/_next.
How do I make this work?
rewrite vs. redirect
Let's first understand this difference.
A rewrite is something that happens on the server only. The server will rewrite the URL that has been requested and search for content of that rewritten URL. The client will not know anything about that. E.g. if /a.html is rewritten to /b.html, the client will receive the same content on both URLs. Client won't know, if there is the same file two times or if one (or both) requests have been rewritten to some other resource.
A redirect on the other hand side involves the client (i.e. browser). If the server is asked for an URL that should be redirected, it will reply back to the client/browser with the destination URL of the rewrite. The client will then send another request for the new URL and make this visible to the end user by changing the address in the navigation bar. If /a.html is redirected to /b.html, the client will not receive the actual content of b.html when requesting a.html, but the browser will update the address to b.html and send a new request.
What's the issue with rewrites in your case?
The HTML contains references to other resources using absolute paths, e.g. something like:
<script src="/_next/static/..."></script>
If this file should be served as docs.example.com and example.com/docs (e.g. using rewrites), the actual HTML will not change. The browser will thus try to access docs.example.com/_next/static/... or example.com/_next/static/... respectively. That works for the first case (docs.example.com), but not for the second one. You've noticed that already.
You can change the basePath of next, e.g. to /docs. Then the HTML would contain <script src="/docs/_next/...">. That would make the browser request docs.example.com/docs/_next/... or example.com/docs/_next/... respectively. That would work for the second case, but not the first one. You could heal the first case with more rewrite rules, but I'd suggest a KISS solution.
Now what?
As mentioned in the comments, placing the exact same content at two different addresses is not good practice. And you can see, that is is causing subsequent difficulties as well. (Not to mention punishment by search engines for duplicate content.)
A good solution would be to decide where to store the content. That should be either docs.example.com or example.com/docs, not both.
Using docs.example.com, forwarding example.com/docs/ to docs.example.com
I'd suggest (and assume in this section) to take docs.example.com to have a clear separation of concerns.
So in Vercel you would set up two projects. One for your "main" next instance, another one for the "docs" next instance. (Both can come from the same repo, that doesn't matter.)
You then assign domains to both projects, e.g. www.example.com to the "main" project, docs.example.com to the "docs" project.
example.com as well as docs.example.com should be working right now.
example.com/docs/ should yield a 404 error.
Then you add redirects (not rewrites!) for your "main" project by adding a vercel.json like this:
{
"redirects": [
{ "source": "/docs/:path*", "destination": "https://docs.example.com/:path*" }
]
}
Now, if you enter example.com/docs/foo in your browser, you should be redirected to docs.example.com/foo and the page should load properly.
Using only example.com/docs/
If you decide to have the docs content only at example.com/docs/, the solution would be as follows:
Add basePath: '/docs' to next.config.js of the docs next project. Do not add a domain to this vercel project.
In the "main" next project add a vercel.json with a rewrite like this:
{
"rewrites": [
{ "source": "/docs", "destination": "https://$domain-of-docs-project.vercel.app/docs" },
{ "source": "/docs/:path*", "destination": "https://$domain-of-docs-project.vercel.app/docs/:path*" }
]
}
Please comment, if you have additional questions or this doesn't fix the issue.
I think you can use the vercel.json like this:
{
"rewrites": [
{
"source": "/:path*",
"has": [
{
"type": "host",
"value": "docs.example.com"
}
],
"destination": "/docs/:path*"
}
]
}
Next offers a specific example for this.
Your "web" project should handle rewrites, as seen here.
next.config.js:
const { DOCS_URL } = process.env //this should be the vercel URL NOT docs.example.com
/** #type {import('next').NextConfig} */
module.exports = {
async rewrites() {
return [
{
source: '/:path*',
destination: `/:path*`,
},
{
source: '/docs',
destination: `${DOCS_URL}/docs`,
},
{
source: '/blog/:path*',
destination: `${BLOG_URL}/docs/:path*`,
},
]
},
}
Your "docs" app needs to set a base path, as seen here. next.config.js:
module.exports = {
basePath: '/docs',
}
Then to ensure that docs.example.com no longer serves the docs as well, I'd just remove the DNS record. Instead you'll just have a vercel URL that you will use in the rewrites in "web". No need to point to that server from docs.example.com.
Nextjs documentation for multi-zone
Nextjs example with multi-zones

Next.JS: How to use localized API routes when running on localhost, or alternatively how to have a single API route for all locales

I'm seeing an issue with my localized Next.js app's API Routes. When I am on a localized version of my site that is running on the localhost, the API routes to my server functions include the locale, which results in 404 errors.
I fetch the route in the client like this:
const res = await fetch(`api/route/path`);
This works when I am on my default locale (en), but if I have a different locale in the url, the fetch fails with a 404:
GET http://localhost:3000/ko/api/route/path 404 (Not Found)
When I deploy my site to Vercel, this issue does not happen. From what I can tell, that's because it deploys serverless functions for every locale I specify in my next.config.js.
My next.config.js
const nextConfig = {
i18n: {
locales: [
'en',
'de',
'ko',
],
defaultLocale: 'en',
},
// ...
};
module.exports = nextConfig;
TL;DR
How can I get localized API routes working in my Next.JS app when running on a localhost? I do not intend on having different behavior on my API for different locales, so is there a way to route all API calls through a single API rather than having serverless functions for each locale?
I found out what the issue was. The API route path in the fetch did not have a forward slash at the start, which was resulting in them not being appended as absolute paths on my domain.
So, the path in the fetch should be /api/route/path, and not api/route/path.
Without the forward slash, the path name was appended relative to the URL, which would include the locale when running on the localhost.

Environmental Variables not working even though it is defined in .env.local file?

I have defined my keys in .env.local but still while calling it process.env.API_KEY it doesn't work as expected.
API_KEY=xx_xxxxxxxxxxxx
DATABASE_URL=https://xxxxx/db/main
Code:
export const xata = new BaseClient({
apiKey: process.env.API_KEY,
databaseURL: process.env.DATABASE_URL
});
Folder Structure:
Project
- pages
- public
- .env.local
- helper (file that needs api key)
In Next.JS, when you add environment variables in your .env.* files whose name don't start with
NEXT_PUBLIC_ , they are only available to the Node.Js environment. So you can access them in API Routes and data fetching methods like GetServerSideProps etc.
If you want to access variables in client browser you have to append NEXT_PUBLIC_.
Your file becomes :
NEXT_PUBLIC_API_KEY=xx_xxxxxxxxxxxx
NEXT_PUBLIC_DATABASE_URL=https://xxxxx/db/main

Automatically generate dynamic Sitemap.xml for Nuxt VueJs Firestore App

I have a site similar to Stackoverflow where users can create a post (or question) which gets its own URL and should be SEO optimized. Therefore I need to include these dynamic pages in my SiteMap.xml. I would like to find an automatic way to insert each dynamic URL to my Sitemap when initially created.
Hoping to not reinvent the wheel, I found sitemap-module for nuxt, however the example they use for dynamic pages is statically written, so not sure what good that does.
I am having a hard time even conceptualizing how to set this up and what is possible with current infrastructure. Can Firestore functions update source code and redeploy or are there any firestore hosting features to help? Could/ should I set up a cron job to run every night to first run a script to query firestore and update sitemap file on local computer, then automatically deploy it to firestore from command line? Any script examples?
Tech used: VueJS, Node.js, Nuxt/ SSR, Firestore (db and hosting), and Express
This is how I did it. Hope this helps. Please share if you managed to get a different solution.
I used npm install #nuxtjs/sitemap
Website here - #nuxtjs/sitemap
In nuxt.config.js
var routes = []
var allUsers = [{'username': 'username'}] // Getting users as an Array
for (var i = 0; i < allUsers.length; i++) {
routeObject = {
'url': '/profile/' + allUsers[i].username
}
routes.push(routeObject);
}
module.exports = {
sitemap: {
path: '/sitemap.xml',
hostname: 'Your hostname here',
cacheTime: 1000 * 60 * 15,
gzip: true,
generate: false,
routes: routes
}
}

Oauth2 Authorization in NelmioApiDocBundle

I am trying to use the NelmioApiDocBundle for a Symfony 3.4 projects API documentation, while also trying to wrap my head around OAuth 2 authorization for the project API access to begin with.
So far I've followed this tutorial on how to get FOSOAuthServerBundle working. So far I can
1.) create a client using the command line command:
php bin/console fos:oauth-server:create-client --redirect-uri="___" --grant-type="authorization_code" --grant-type="password" --grant-type="refresh_token" --grant-type="token" --grant-type="client_credentials"
2.) I can also get an access token manually by visiting this url on my server
http://127.0.0.1:8000/oauth/v2/token?client_id=______&client_secret=________&grant_type=client_credentials
3.) I can use the token to access areas of my Symfony project requiring OAuth Access by including the token in a GET parameter
However, in the NelmioApiDocBundle Authorizations I cannot get this to work to completion. Here is a screenshot:
If enter my client_id and secret key it takes me to the Login Page, as expected. I can enter my login information and in takes me to the Approve or Deny Page, as expected. At this point if I click either Approve or Deny it tries to use a "redirect_uri" of http://localhost:3200/oauth2-redirect.html. No matter what I do I cannot change the redirect URI.
How to I get the a proper redirect URI?
Ok, this was actually easily fixed. You need to add a single line:
oauth2RedirectUrl: 'URLhere',
to the file init-swagger-ui.js which is located (Symfony 3.4) in web/bundles/nelmioapidoc/
The final file ended up looking like this:
window.onload = () => {
const data = JSON.parse(document.getElementById('swagger-data').innerText);
const ui = SwaggerUIBundle({
oauth2RedirectUrl: 'URLhere',
spec: data.spec,
dom_id: '#swagger-ui',
validatorUrl: null,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: 'StandaloneLayout'
});
window.ui = ui;
};
Also you likely are going to want to download the file oauth2-redirect.html from the Swagger project to include for the actual redirect.

Resources