GithubHelp home page GithubHelp logo

vercel / platforms Goto Github PK

View Code? Open in Web Editor NEW
5.2K 113.0 651.0 6.78 MB

A full-stack Next.js app with multi-tenancy and custom domain support. Built with Next.js App Router and the Vercel Domains API.

Home Page: https://app.vercel.pub

JavaScript 3.94% CSS 2.33% TypeScript 93.73%
nextjs vercel tailwindcss prisma custom-domains multi-tenancy multi-tenant-applications vercel-ai vercel-postgres vercel-blob

platforms's Introduction

Platforms Starter Kit

The all-in-one starter kit
for building multi-tenant applications.

Introduction · Demo · Deploy Your Own · Guide · Kitchen Sink · Contributing


Introduction

The Platforms Starter Kit is a full-stack Next.js app with multi-tenancy and custom domain support. Built with Next.js App Router, Vercel Postgres and the Vercel Domains API.

Here's a quick 30-second demo:

Platforms.v2.mp4

Features

  1. Multi-tenancy: Programmatically assign unlimited custom domains, subdomains, and SSL certificates to your users using the Vercel Domains API
  2. Performance: Fast & beautiful blog posts cached via Vercel's Edge Network, with the ability to invalidate the cache on-demand (when users make changes) using Incremental Static Regeneration + Next.js' revalidateTag API
  3. AI Editor: AI-powered Markdown editor for a Notion-style writing experience powered by Novel
  4. Image Uploads: Drag & drop / copy & paste image uploads, backed by Vercel Blob
  5. Custom styles: Custom fonts, 404 pages, favicons, sitemaps for each site via the Next.js file-based Metadata API
  6. Dynamic OG Cards: Each blog post comes with a dynamic OG image powered by @vercel/og
  7. Dark Mode: For a better user experience at night
  8. Multi-tenant Preview URLs: Preview changes to your client sites using Vercel Preview URLs. Learn more.
Demo

Deploy Your Own

Deploy your own version of this starter kit with Vercel.

Deploy with Vercel

You can also read the guide to learn how to develop your own version of this template.

What is a multi-tenant application?

Multi-tenant applications serve multiple customers across different subdomains/custom domains with a single unified codebase.

For example, our demo is a multi-tenant application:

Another example is Hashnode, a popular blogging platform. Each writer has their own unique .hashnode.dev subdomain for their blog:

Users can also map custom domains to their .hashnode.dev subdomain:

With the Platforms Starter Kit, you can offer unlimited custom domains at no extra cost to your customers as a premium feature, without having to worry about custom nameservers or configuring SSL certificates.

Examples of platforms

Vercel customers like Hashnode, Super, and Cal.com are building scalable platforms on top of Vercel and Next.js. There are multiple types of platforms you can build with this starter kit:

1. Content creation platforms

These are content-heavy platforms (blogs) with simple, standardized page layouts and route structure.

“With Vercel, we spend less time managing our infrastructure and more time delivering value to our users.” — Sandeep Panda, Co-founder, Hashnode

  1. Hashnode
  2. Mintlify
  3. Read.cv

2. Website & e-commerce store builders

No-code site builders with customizable pages.

By using Next.js and Vercel, Super has fast, globally distributed websites with a no-code editor (Notion). Their customers get all the benefits of Next.js (like Image Optimization) without touching any code.

  1. Super.so
  2. Typedream
  3. Makeswift

3. B2B2C platforms

Multi-tenant authentication, login, and access controls.

With Vercel and Next.js, platforms like Instatus are able to create status pages that are 10x faster than competitors.

  1. Instatus
  2. Cal.com
  3. Dub

Built on open source

This working demo site was built using the Platforms Starter Kit and:

Contributing

  • Start a discussion with a question, piece of feedback, or idea you want to share with the team.
  • Open an issue if you believe you've encountered a bug with the starter kit.

Author

License

The MIT License.


platforms's People

Contributors

0o001 avatar adnan0061 avatar amarpathak avatar arig4m3r avatar balazsorban44 avatar brkalow avatar cezarneaga avatar chroxify avatar dependabot[bot] avatar devmaxc avatar diogoduart3 avatar futantan avatar guidovizoso avatar harajune avatar itzcrazykns avatar iuzn avatar jamessingleton avatar janpio avatar joaovsa7 avatar jonoise avatar jpvalery avatar leerob avatar lfades avatar mertcanaltin avatar nicholasgriffintn avatar nurodev avatar olistic avatar ramnavan avatar ruheni avatar steven-tey avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

platforms's Issues

500 server error when trying to access a custom domain - custom domain issue

Hi, I'm working on a platform using Next.js 12.0.11-canary.8

This is my case

  • The main domain is: maindomain.com in production and localhost:3000 in development

  • Each user gets a random subdomain like: gogdf.maindomain.com or gogdf.localhost:3000 in development

  • Each user can add a custom domain that they own from a registrar (for example namecheap) and the domain is added successfully to the team's project (the platform I'm working on)

  • After that the user points their domain DNS configurations to vercel's nameservers or an A record 76.76.21.21

  • Everything is working fine, the domain is added to Vercel and points to Vercel

  • When I try to access the custom domain (for example newdomain.com) home page (newdomain.com/) the page works as expected, BUT when I try to access any page that's not the homepage (root, '/') or any pathname like newdomain.com/signup is gives me an error 404, and when I try to access a page that requires a logged-in/session user such as newdomain.com/myaccount it gives me a 500 server error

Here's my _middleware.js file to check if there's an error with my current state that make the main domain (maindomain.com) works fine, the subdomains (usersite.maindomain.com) works fine, the custom domains (newdomain.com/) home page only also works fine, but prevents the custom domains paths other than homepage to work properly.

_middleware.js

import { NextResponse } from "next/server";

export default function middleware(req) {
  const url = req.nextUrl.clone(); // clone the request url
  const { pathname } = req.nextUrl; // get pathname of request (e.g. /blog-slug)
  const hostname = req.headers.get("host"); // get hostname of request (e.g. demo.vercel.pub)

  // only for demo purposes – remove this if you want to use your root domain as the landing page
  // if (hostname === "vercel.pub" || hostname === "platforms.vercel.app") {
  //   return NextResponse.redirect("https://demo.vercel.pub");
  // }

  const currentHost =
    process.env.NODE_ENV === "production" && process.env.VERCEL === "1"
      ? hostname.replace(`.maindomain.com`, "")
      : hostname.replace(`.localhost:3000`, "");

  if (!pathname.includes(".") && !pathname.startsWith("/api")) {
    if (currentHost == "app") {
      if (
        (pathname === "/login" || pathname === "/signup") &&
        (req.cookies["next-auth.session-token"] ||
          req.cookies["__Secure-next-auth.session-token"])
      ) {
        url.pathname = "/";
        return NextResponse.redirect(url);
      }
      url.pathname = `/app${pathname}`;
      return NextResponse.rewrite(url);
    }
    if (currentHost == "admin") {
      url.pathname = `/admin${pathname}`;
      return NextResponse.rewrite(url);
    }
    if (currentHost == "patient") {
      url.pathname = `/patient${pathname}`;
      return NextResponse.rewrite(url);
    }
    if (currentHost == "doctor") {
      url.pathname = `/doctor${pathname}`;
      return NextResponse.rewrite(url);
    }
    if (
      pathname === "/login" &&
      (req.cookies["next-auth.session-token"] ||
        req.cookies["__Secure-next-auth.session-token"])
    ) {
      url.pathname = "/";
      return NextResponse.redirect(url);
    }
    if (hostname === "localhost:3000" || hostname === "maindomain.com") {
      url.pathname = `/home${pathname}`;
      return NextResponse.rewrite(url);
    }
    // tried to replace {currentHost} with {hostname} but same issue persist. 
    url.pathname = `/_sites/${currentHost}${pathname}`;  
    return NextResponse.rewrite(url);
  }
}

Feel free to ask me anything or to provide more information about my situation.

I'm waiting for your response, I need to fix this as soon as possible.

Thank you.

Following tutorials resulted in 404 not found error

Hi! I'm very excited about the multi-tenancy feature and tried the official tutorial below.
https://vercel.com/guides/nextjs-multi-tenant-application
However, after finishing the step5, the example site only shows 404 not found.

The example works well on local:3000.
Could anyone help me to make the example work?

Also, I'm not familiar with Vercel. So, it is very appreciated to let me know the good way to debug this kind of problem.

The details are as below.

Thanks

NuxtJS Support

Any plans to add a Nuxt example for this? I'd love to switch over to Vercel and use this but I'm hoping to continue using Nuxt which now supports incremental static generation.

Mention minimal node version

Today I've problem related to next-auth while following the guide.
The issue was I need to use other node version which is node >= v14

I guess it should be mentioned in the guide or this readme repo,
to prevent confusion, in case someone face it

Convert to TypeScript

Prisma works best with TypeScript, and we think it's a great way to build a platform. Let's move there!

Invalid domain setup instructions for subdomains

When configuring a top level domain, eg. example.com, the setup instructions displayed by DomainCard are correct, showing the necessary records for both example.com (the A record) and www.example.com (the www CNAME record).

However, the instructions are wrong when using a subdomain, eg sub.example.com:

image

Here, the CNAME record is wrong: it should be sub, not www. And the A record is not applicable.

It seems to me that it should be:

image

Fix empty site name bug

What

Currently when creating a new site the name field is not required and as such it's possible to create a site with no name. There's a multitude of reasons this is not ideal, one of which being if you go delete the site it will recommend you enter the sites name, which is just an empty string so there is no real 2nd confirmation before deletion.

subdomain not validating public paths?

I'm encountering a strange error when trying to load images from a subdomain.

I have followed the exact steps in the repo for middleware, with my own components and pages.

When the server tries to GET an image, it responds with 404 that the image cannot be found.

Request URL: https://app.xxxxxxxxxx.com/favicon.ico
Request Method: GET
Status Code: 404 

Where as on the main domain:

https://xxxxxxxxxx.com/favicon.ico

Resolves without any issues what so ever.

What am I missing here?

Add ESLint config

Currently there is no ESLint config present, so running the lint script does nothing but prompt you to add one.
Would it be worth adding the default strict Next.js lint config?

Integrate with Magic

It would be great to see Magic integration for frictionless, passwordless login using email magic links.

While NextAuth.js supports passwordless auth via the Email provider, Magic provides blockchain based security and minimal setup that makes it easy to get up and running (NextAuth.js requires configuring your database for magic email links).

error - TypeError: Expected "3000" to be a string

Launching the app with npm run dev.
Accessing it via: http://192.168.0.107:3000 from a different machine no the same subnet.
Get this in the logs on the server:

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
(node:31041) ExperimentalWarning: The ESM module loader is experimental.
event - compiled client and server successfully in 314 ms (163 modules)
warn  - using beta Middleware (not covered by semver) - https://nextjs.org/docs/messages/beta-middleware
wait  - compiling /_middleware...
event - compiled client and server successfully in 136 
error - TypeError: Expected "3000" to be a string

And every time I refresh the page in the browser I get the error - TypeError: Expected "3000" to be a string error.

i18n locales 404 issues

Hello. I have issues with i18n locales. I currently have only 2 locales, but when turned on in next.config all paths renders as 404 when deployed to Vercel. Locally everything works as expected.

I have tried several approaches, tried to add basePath as someone suggested like for example:

url.pathname = `${basePath}/_sites/${currentHost}${pathname}`;
    return NextResponse.rewrite(url);

It did nothing, basically.

Tried to add locale too as someone suggested, for example:

url.pathname = `${basePath}/${locale}/_sites/${currentHost}${pathname}`;
    return NextResponse.rewrite(url);

and it somewhat works. Default locale works, but on some pages secondary locale gives 404 still, like:

subdomain.domain.something/secondarylocale
subdomain.domain.something/secondarylocale/somepage

if I add en, which is default locale it works:

subdomain.domain.something/en
subdomain.domain.something/en/somepage

Full _middleware:

import { NextResponse } from "next/server";

import type { NextRequest } from "next/server";

export default function middleware(req: NextRequest) {
  // Clone the request url
  const url = req.nextUrl.clone();

  // Get pathname of request (e.g. /blog-slug)
  const { pathname, basePath } = req.nextUrl;
  const locale = req.nextUrl.locale;

  // Get hostname of request (e.g. demo.xy.com)
  const hostname = req.headers.get("host");
  if (!hostname)
    return new Response(null, {
      status: 400,
      statusText: "No hostname found in request headers",
    });

  // Only for demo purposes – remove this if you want to use your root domain as the landing page
  // if (
  //   hostname === "xy.com" ||
  //   hostname === "platforms.vercel.app"
  // ) {
  //   return NextResponse.redirect("https://demo.xy.com");
  // }

  const PUBLIC_FILE = /\.(.*)$/;

  const currentHost =
    process.env.NODE_ENV === "production" && process.env.VERCEL === "1"
      ? // You have to replace ".xy.com" with your own domain if you deploy this example under your domain.
        // You can use wildcard subdomains on .vercel.app links that are associated with your Vercel team slug
        // in this case, our team slug is "platformize", thus *.platformize.vercel.app works
        hostname.replace(`.xy.com`, "")
      : hostname.replace(`.localhost:3000`, "");

  console.log(
    "req",
    locale,
    // "end",
    "basePath",
    basePath,
    "end",
    process.env.NODE_ENV,
    process.env.VERCEL,
    "host",
    req.headers.get("host"),
    "current host",
    currentHost
  );

  if (pathname.startsWith(`/_sites`))
    return new Response(null, {
      status: 404,
    });

  if (
    !pathname.includes(".") &&
    !pathname.startsWith("/api") &&
    !PUBLIC_FILE.test(req.nextUrl.pathname)
  ) {
    if (currentHost == "app") {
      if (
        pathname === "/login" &&
        (req.cookies["next-auth.session-token"] ||
          req.cookies["__Secure-next-auth.session-token"])
      ) {
        url.pathname = `${basePath}/${locale}`;
        return NextResponse.redirect(url);
      } else if (!pathname) {
        url.pathname = `${basePath}/${locale}/_sites/${currentHost}`;
        return NextResponse.rewrite(url);
      }

      url.pathname = `${basePath}/${locale}/app${pathname}`;
      return NextResponse.rewrite(url);
    }

    if (hostname === "localhost:3000" || hostname === "xy.com") {
      url.pathname = `${basePath}/${locale}/home${pathname}`;
      return NextResponse.rewrite(url);
    }

    url.pathname = `${basePath}/${locale}/_sites/${currentHost}${pathname}`;
    return NextResponse.rewrite(url);
  }
}

Router.push using the shallow option causes hard redirect

When using a setup with the _sites/[site] structure using router.push or router.rewrite with shallow: true causes a hard redirect, using window.location.href.

e.g.

router.push({ query: { s: ‘test’ }}, undefined, { shallow: true})

causes the full page to be hard refreshed instead of updating the search query without reloading.

I’m using { shallow: false, scroll: false } as a workaround, but it’s still not taking advantage of the shallow update.

Error

So far I'm managed to trace the issue to this function crashing

https://github.com/vercel/next.js/blob/93678b569bb46df9fad39f9db2b18797872b7c15/packages/next/shared/lib/router/router.ts#L1266

where inside getRouteInfo, fetchComponent crashes with Error: Failed to load script: /_next/static/chunks/pages/index.js when the file that it should be loading is _next/static/chunks/pages/_sites/[site].js

Switch from Twitter to GitHub OAuth

Overall, GitHub OAuth has a lot fewer steps to get set up than Twitter OAuth. I think this would help those trying to get the project set up and running locally. Plus, with next-auth, you can always switch to other providers if you want.

Thoughts?

Requesting domain delegation deprecated?

We're using Vercel Platforms at Instatus and loving it.

Noticed that domain delegation endpoint is deprecated.
Is there an alternative way to display to customers what do they need to do in case their domain is used by another team?

CORS error when trying to trigger custom revalidation for ISR pages

Hey, I'm trying to do custom revalidation for my static pages, and I've followed the example given in the project, but every time I try to validate I get a CORS error

Screenshot 2022-07-24 at 22 23 11

Screenshot 2022-07-24 at 22 30 22

Essentially, I'm trying to revalidate a page on a custom domain from the main app (a different domain).

My revalidate client fetch call (called from the core app and not the subdomain)

  if (subdomain) {
        await revalidate(
          `https://${subdomain}.myapp.com`, // hostname to be revalidated
          subdomain, // siteId
          "/"
        );
}

The revalidate function that calls the fetch

export async function revalidate(
  hostname: string, // hostname to be revalidated
  siteId: string, // siteId
  slug: string // slugname for the post
) {
  const urlPaths = [`/_sites/${siteId}/${slug}`, `/_sites/${siteId}`];
  try {
    await Promise.all(
      urlPaths.map((urlPath) =>
        fetch(`${hostname}/api/revalidate`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            urlPath,
          }),
        })
      )
    );
  } catch (err) {
    console.error(err);
  }
}

The pages/api/revalidate.ts

import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { urlPath } = req.body;
  try {
    await res.revalidate(urlPath);

    return res.status(200).json({
      message: "OK",
    });
  } catch (error) {
    res.status(500).json({
      message: `Failed to revalidate "${urlPath}"`,
    });
  }
}

Through some googling, I found that you cant set CORS access headers for all of your APIs by following this article https://vercel.com/support/articles/how-to-enable-cors

My next.config.js

module.exports = {
  reactStrictMode: true,
  async headers() {
    return [
      {
        // matching all API routes
        source: "/api/:path*",
        headers: [
          { key: "Access-Control-Allow-Credentials", value: "true" },
          { key: "Access-Control-Allow-Origin", value: "*" },
          { key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
          { key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" },
        ]
      }
    ]
  }
}

But even so every time I try to call the revalidate API I get the following errors as you can see in the first images.

Any help would be appreciated!

Access control on API ?

I might be missing something, but it seems to me that the API is insecure, the api routes don’t check anything, so anyone can issue requests to delete or modify any existing content from other spaces for example.

Update code links for "Image Uploads with Cloudinary" section on guides post "How to Build a Multi-Tenant App with Custom Domains Using Next.js"

On vercel.com there is a post called "How to Build a Multi-Tenant App with Custom Domains Using Next.js" the source codes links for the section on Image Uploads with Cloudinary are for the JavaScript files and not the TypeScript files. So a user will get a 404 page when visiting them.

If the post page is generated from a file on GitHub I am happy to update it. I couldn't find it myself.

Router.push Shallow writing wrong URL using Middleware NextResponse.rewrite(url)

I am using the platform code to write a multitenat application.

When I perform a shallow router push in one of the subdomain URLs the url writes wrong extra information

Working in localhost with subdomains

Original URL http://casablanca.nomadtest.com:3001/room/20991d5b-c321-4b75-9c01-4fc85e7a4c3d

Run the Router.push

          router.push(
            {
              query: {
                site: router.query.site,
                id: router.query.id,
                start_date: "12-12-2022",
              },
            },
            undefined,
            {
              shallow: true,
            }
          );

This provoke a whole page hard reload

Resulting URL http://casablanca.nomadtest.com:3001/_sites/casablanca/room/20991d5b-c321-4b75-9c01-4fc85e7a4c3d?start_date=12-12-2022

It add extra "_sites/casablanca/"

Expected URL: http://casablanca.nomadtest.com:3001/room/20991d5b-c321-4b75-9c01-4fc85e7a4c3d?start_date=12-12-2022

Avoid page reload

Here is the middleware handling the rewrite

export default function middleware(req: NextRequest) {
  const url = req.nextUrl.clone();
  const { pathname } = req.nextUrl;
  const hostname = req.headers.get("host");
  const currentHost = hostname!.replace(`.nomadtest.com:3001`, "");

  if (pathname.startsWith(`/_sites`)) {
    return new Response(null, { status: 404 });
  }

  if (!pathname.includes(".") && !pathname.startsWith("/api")) {
    if (currentHost === "host") {
      url.pathname = `/host${pathname}`;
      return NextResponse.rewrite(url);
    } else if (hostname === "nomadtest.com:3001") {
      return NextResponse.rewrite(url);
    } else {
      url.pathname = `/_sites/${currentHost}${pathname}`;
      return NextResponse.rewrite(url);
    }
  }
  return NextResponse.rewrite(url);
}

Error: Get config: Schema Parsing P1012

When running npx prisma migrate dev --name init I am getting the following error:

Prisma schema loaded from prisma/schema.prisma
Error: Get config: Schema Parsing P1012

error: Environment variable not found: DATABASE_URL.
  -->  schema.prisma:6
   | 
 5 |   provider             = "mysql"
 6 |   url                  = env("DATABASE_URL")
   | 

Validation Error Count: 1

And the migration is not loading into planetscale staging branch or anywhere else for that matter.

Any thoughts?

Thanks

Nested path inside _sites is not working

I tried to put the specific path for custom domains, like below

my-domain.localhost:3000/posts/nextjs-awesome -> returns 404

My project
| _sites
| ----posts
|---index.tsx
|----[slug].tsx

It does not work. Can someone tell me why?

"ikm" must be at least one byte in length

Any thoughts on what is the issue?

thanks
Hany
https://next-auth.js.org/warnings#no_secret
[next-auth][warn][NO_SECRET]
https://next-auth.js.org/warnings#no_secret
[next-auth][error][GET_AUTHORIZATION_URL_ERROR]
https://next-auth.js.org/errors#get_authorization_url_error "ikm" must be at least one byte in length {
message: '"ikm" must be at least one byte in length',
stack: 'TypeError: "ikm" must be at least one byte in length\n' +
' at normalizeIkm (/Users/hanymorcos/projects/platforms/node_modules/@panva/hkdf/dist/node/cjs/index.js:26:15)\n' +
' at hkdf (/Users/hanymorcos/projects/platforms/node_modules/@panva/hkdf/dist/node/cjs/index.js:47:60)\n' +

Help me deploy multitenant app

Hi there
Have platform up and running on domain https://fastep-multitenant-platform.vercel.app/

But got error on additional domains https://fastep-multitenant-platform-it-lead-team.vercel.app/

SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
    NextJS 36
framework-c4676fea5eba2f84.js:1:82741

SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
    NextJS 36
main-5e1ee2c15afa1ec8.js:1:17781

Please help, even don't know what is wrong.

Config:

import { NextRequest, NextResponse } from "next/server";

export const config = {
  matcher: [
    "/",
    "/([^/.]*)", // exclude `/public` files by matching all paths except for paths containing `.` (e.g. /logo.png)
    "/site/:path*", // for app.vercel.pub/site/[siteId]
    "/post/:path*", // for app.vercel.pub/post/[postId]
    "/_sites/:path*", // for all custom hostnames under the `/_sites/[site]*` dynamic route (demo.vercel.pub, platformize.co)
  ],
};

export default function middleware(req: NextRequest) {
  const url = req.nextUrl;

  // Get hostname of request (e.g. demo.vercel.pub, demo.localhost:3000)
  const hostname = req.headers.get("host") || "demo.vercel.pub";

  // Only for demo purposes – remove this if you want to use your root domain as the landing page
  // if (hostname === "vercel.pub" || hostname === "platforms.vercel.app") {
  //   return NextResponse.redirect("https://demo.vercel.pub");
  // }

  /*  You have to replace ".vercel.pub" with your own domain if you deploy this example under your domain.
      You can also use wildcard subdomains on .vercel.app links that are associated with your Vercel team slug
      in this case, our team slug is "platformize", thus *.platformize.vercel.app works. Do note that you'll
      still need to add "*.platformize.vercel.app" as a wildcard domain on your Vercel dashboard. */
  const currentHost =
    process.env.NODE_ENV === "production" && process.env.VERCEL === "1"
      ? hostname
          .replace(`.vercel.app`, "")
          .replace(`.fastep-multitenant-platform.vercel.app`, "")
      : hostname.replace(`.localhost:3000`, "");

  // rewrites for app pages
  if (currentHost == "app") {
    if (
      url.pathname === "/login" &&
      (req.cookies.get("next-auth.session-token") ||
        req.cookies.get("__Secure-next-auth.session-token"))
    ) {
      url.pathname = "/";
      return NextResponse.redirect(url);
    }

    url.pathname = `/app${url.pathname}`;
    return NextResponse.rewrite(url);
  }

  // rewrite root application to `/home` folder
  if (hostname === "localhost:3000" || hostname === "fastep-multitenant-platform.vercel.app") {
    url.pathname = `/home${url.pathname}`;
    return NextResponse.rewrite(url);
  }

  // rewrite everything else to `/_sites/[site] dynamic route
  url.pathname = `/_sites/${currentHost}${url.pathname}`;
  return NextResponse.rewrite(url);
}

How the On-demand ISR work for multiple subdomains?

I noticed a great feature On-demand ISR, but I'm not sure how to make it work for multiple subdomains.
I read the source code of this repo, but the usage is:

res.unstable_revalidate(urlPath);

What if two sites have the same urlPath? For example alice.github.com/hello and bob.github.com/hello

issue with MDXRemote.

I was getting errors after vercel deployment. I installed error bound package and received the following error message.

thanks,

Hany

./pages/_sites/[site]/[slug].tsx:129:40
Type error: Type '{ a: (options: { href: string; } & { children?: ReactNode; }) => JSX.Element; BlurImage: (props: BlurImageProps) => JSX.Element; Examples: ({ data }: ExamplesProps) => JSX.Element; Tweet: ({ id, metadata, className }: TweetProps) => JSX.Element; }' is not assignable to type 'Record<string, ReactNode>'.
Property 'a' is incompatible with index signature.
Type '(options: { href: string; } & { children?: ReactNode; }) => Element' is not assignable to type 'ReactNode'.

127 |
128 |

129 | <MDXRemote {...data.mdxSource} components={components} />
| ^
130 |


131 |
132 | {adjacentPosts.length > 0 && (
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Application error when trying to access a post

I created the following test site : Test3

And then published a post with this markdown content : pastebin

When I'm trying to access the post here, I encounter a Application Error.

Application error: a client-side exception has occurred (see the browser console for more information).

Here's my console output :

Screenshot 2022-01-21 at 13 46 32

Passing wrong argument on site>setting.tsx

In line 264 of setting.tsx it the code is passing in the VALUE of the html input element instead of EVENT
await handleCustomDomain(e.currentTarget.customDomain.value);

In line 154 of setting.tsx, it is extracting the value again
async function handleCustomDomain(event: FormEvent<HTMLFormElement>) { const customDomain = event.currentTarget.customDomain.value;

The solution:
I change line 264 to
await handleCustomDomain(e.currentTarget.customDomain.value);

and I am able to add custom domain successfully

Next.js 12.1.0 removed redirecting to relative URL in Middleware

After upgrading to Next.js 12.1.0, the code in the _middleware.js that redirects to relative "/" after login breaks.

This line: return NextResponse.redirect("/");

I have tried following the documentation and rewriting to "/", "", and a few others, but they all just throw a 404.

The repo needs updated with code that works in 12.1.0. If someone has feedback here I am happy to implement, test, and open a PR.

Production subdomains have empty props from getStaticProps

I added some platforms code to my app and everything was working fine until recently and now my subdomains on my production domain don't work.

Here's what my component looks like in /pages/_sites/[site]/index.js

const Index = (props) => {
  const router = useRouter();

  if (router.isFallback) {
    return <h1>Loading...</h1>;
  }
  console.log("compoennt: ");
  console.log(typeof props.site);
  console.log({ props });

  if (!props.site) return <p>error...</p>;
  const site = JSON.parse(props.site);
  if (!site.site) return <p>error...</p>;
...

On my production site I get an empty props:

image

But on localhost I get the information I expect:

image

And in my Vercel build logs, I console.log my getStaticPaths and getStaticProps and I see all the data I expect:

image

But when I visit my website like subdomain.leavewithaweb.site I see an empty props object and my site doesn't work or have the data even though it's building?

It was working like a week ago then something happened and it stopped working.

My _middleware.js

export default function middleware(req) {
  // Clone the request url
  const url = req.nextUrl.clone();

  // Get pathname of request (e.g. /blog-slug)
  const { pathname } = req.nextUrl;

  // Get hostname of request (e.g. demo.vercel.pub)
  const hostname = req.headers.get("host");
  if (!hostname)
    return new Response(null, {
      status: 400,
      statusText: "No hostname found in request headers",
    });

  const currentHost =
    process.env.NODE_ENV === "production" && process.env.VERCEL === "1"
      ? // You have to replace ".vercel.pub" with your own domain if you deploy this example under your domain.
        // You can use wildcard subdomains on .vercel.app links that are associated with your Vercel team slug
        // in this case, our team slug is "platformize", thus *.platformize.vercel.app works
        hostname.replace(`.leavewithaweb.site`, "")
      : hostname.replace(`.localhost:3000`, "");

  if (pathname.startsWith(`/_sites`))
    return new Response(null, {
      status: 404,
    });

  if (!pathname.includes(".") && !pathname.startsWith("/api")) {
    if (hostname === "localhost:3000" || hostname === "leavewithaweb.site") {
      if (
        pathname === "/" &&
        (req.cookies["next-auth.session-token"] ||
          req.cookies["__Secure-next-auth.session-token"])
      ) {
        url.pathname = `/home`;
        return NextResponse.rewrite(url);
      } else if (pathname === "/") {
        url.pathname = `/home`;
        return NextResponse.rewrite(url);
      }
    } else {
      url.pathname = `/_sites/${currentHost}${pathname}`;
      return NextResponse.rewrite(url);
    }
  }
}

My getStaticPaths and getStaticProps

export async function getStaticPaths() {
  const subdomains = await fetch(
    `${process.env.NEXT_PUBLIC_SERVER_URL}zzz`,
    {
      method: "GET",
    }
  ).then((res) => res.json());

  console.log(subdomains);

  if (subdomains.subdomains.length === 0) {
    return {
      paths: [],
      fallback: true,
    };
  } else {
    return {
      paths: subdomains.subdomains.map((subdomain) => {
        return {
          params: { site: subdomain },
        };
      }),
      fallback: "blocking",
    };
  }
}

export async function getStaticProps({ params: { site } }) {
  const siteRes = await fetch(
    `${process.env.NEXT_PUBLIC_SERVER_URL}/zzz`,
    {
      method: "GET",
    }
  ).then((res) => res.json());

  console.log(siteRes);

  return {
    props: {
      site: JSON.stringify(siteRes),
    },
    revalidate: 10,
  };
}

req.nextUrl.clone is not a function

Seeing this issue running dev locally in the _middleware.js

I'm unsure why I am having this issue, as I have brought parts of this repo into an existing Next.js project. Let me know what specific details I need to share and I will update the issue accordingly.

image

Twitter Auth Essential Access error

I'm getting the following error.

[next-auth][error][OAUTH_V1_GET_ACCESS_TOKEN_ERROR] 
https://next-auth.js.org/errors#oauth_v1_get_access_token_error undefined {
  statusCode: 403,
  data: '{"errors":[{"message":"You currently have Essential access which includes access to Twitter API v2 endpoints only. If you need access to this endpoint, you’ll need to apply for Elevated access via the Developer Portal. You can learn more here: https://developer.twitter.com/en/docs/twitter-api/getting-started/about-twitter-api#v2-access-leve","code":453}]}\n'
}

I am following along with the blog post but things do look different for me. For example, my Twitter dev dashboard does not have "enable 3-legged" option. Just the options in the screenshot below

image

I can't even get the login button to prompt for Twitter oAuth without changing the callback URL in step 13 from the blog post suggestion of 'http://localhost:3000/api/auth/callback/twitter' (does not work) to 'http://app.localhost:3000/api/auth/callback/twitter' (prompts but get console error above)

If I follow the blog post exactly I get this

[next-auth][error][GET_AUTHORIZATION_URL_ERROR] 
https://next-auth.js.org/errors#get_authorization_url_error undefined {
  statusCode: 403,
  data: `<?xml version='1.0' encoding='UTF-8'?><errors><error code="415">Callback URL not approved for this client application. Approved callback URLs can be adjusted in your application settings</error></errors>`
}
[next-auth][error][SIGNIN_OAUTH_ERROR] 
https://next-auth.js.org/errors#signin_oauth_error undefined {
  error: {
    statusCode: 403,
    data: `<?xml version='1.0' encoding='UTF-8'?><errors><error code="415">Callback URL not approved for this client application. Approved callback URLs can be adjusted in your application settings</error></errors>`
  },
  provider: {
    id: 'twitter',
    name: 'Twitter (Legacy)',
    type: 'oauth',
    version: '1.0A',
    authorization: { url: 'https://api.twitter.com/oauth/authenticate', params: {} },
    accessTokenUrl: 'https://api.twitter.com/oauth/access_token',
    requestTokenUrl: 'https://api.twitter.com/oauth/request_token',
    profileUrl: 'https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true',
    profile: [Function: profile],
    clientId: 'xxxx-removed-xxxx',
    clientSecret: 'xxxx-removed-xxxx',
    signinUrl: 'http://app.localhost:3000/api/auth/signin/twitter',
    callbackUrl: 'http://app.localhost:3000/api/auth/callback/twitter'
  },
  message: undefined
}

Any help or suggestions is appreciated

API Revalidate Example urlPath

Hello,

I see there is a handler to use on-demand incremental static regeneration which is an awesome feature. I cannot seem to get it working on this platforms example? I've used this feature where the path is straightforward and it works great.

What is the path supposed to look like for Platforms? I see this in the api handler with...

await res.unstable_revalidate(urlPath);

I've tried in the body of the post request...

"urlPath": "/_sites/name-of-site" (using the subdomain or customDomian)
"urlPath": "/_sites"
"urlPath": "/"
"urlPath": "/_sites/name-of-site/post-id" (using the slug)

What am I missing here? How should the urlPath look in the request? Or is there some other issue I'm missing in the middleware?

Were you able to test the api/revalidate feature when deployed to Vercel?

Thanks!

Error with Cloudinary upload widget

I would like to use eslint with next/core-web-vitals but it causes build fail with below:

./components/Cloudinary.js
39:11  Error: External synchronous scripts are forbidden. See: https://nextjs.org/docs/messages/no-sync-scripts  @next/next/no-sync-scripts

AFAIK, external synchronous scripts can't be used and placed in Head. How to fix it, or any recommendation for workaround?

Revalidation is not working

When I build locally by next build and next start I can revalidate and it works, however, when I deploy this to vercel it won't revalidate anymore.

Weird is, that revalidation only works with /_sites/blog/{id} instead of /{id} even though the rewrite is set in middleware and the validation happens on blog.domain.com. I get "OK" on the /_site.. requests and failed to revalidate on everything else.

When I revalidate after deploying (using /_sites/...) I can disable the cache and refresh the page and for a split second I see the new version but than it changes back to the old one.

I use this for a fixed path blog so I changed the getStaticPaths/ Props:

pages/_sites/[site]/index

export const getStaticPaths = async () => {
  return { paths: [{ params: { site: "blog" }}], fallback: true }
};

export const getStaticProps = async ({
  params,
}) => {
  const data = (await prisma.post.findMany({
    where: {
      published: true,
    },
    orderBy: {
      createdAt: "desc",
    },
  }));

  if (!data) return { notFound: true, revalidate: 10 };

  return {
    props: {
      stringifiedData: JSON.stringify({
        [...]
      }),
    },
    revalidate: 3600,
  };
};

pages/_sites/[site]/[slug]

export const getStaticPaths = async () => {
  const posts = await prisma.post.findMany({
    select: {
      slug: true,
    },
  });

  const paths = posts.flatMap((post) => {
    return {
      params: {
        site: "blog",
        slug: post.slug,
      },
    };
  })

  return {
    paths,
    fallback: true,
  };
};


export const getStaticProps = async ({
  params,
}) => {
  if (!params) throw new Error("No path parameters found");

  const { slug } = params;

  const data = (await prisma.post.findFirst({
    where: {
      slug
    }
  }));

  if (!data) return { notFound: true, revalidate: 10 };

  const [mdxSource, adjacentPosts] = await Promise.all([
    getMdxSource(data.content),
    prisma.post.findMany({
      where: {
        published: true,
        NOT: {
          id: data.id,
        },
      },
      select: {
        slug: true,
        title: true,
        createdAt: true,
        description: true,
        image: true,
        imageBlurhash: true,
      },
    }),
  ]);

  return {
    props: {
      stringifiedData: JSON.stringify({
        ...data,
        mdxSource,
      }),
      stringifiedAdjacentPosts: JSON.stringify(adjacentPosts),
    },
    revalidate: 3600,
  };
};

And middleware

export const config = {
  matcher: [
    "/",
    "/([^/.]*)", // exclude `/public` files by matching all paths except for paths containing `.` (e.g. /logo.png)
    "/site/:path*",
    "/post/:path*",
    "/_sites/:path*",
  ],
};

export default function middleware(req) {
  // Clone the request url
  const url = req.nextUrl.clone();
  
  const { pathname } = req.nextUrl;
  
  const hostname = req.headers.get("host");
  if (!hostname)
    return new Response(null, {
      status: 400,
      statusText: "No hostname found in request headers",
    });


  const currentHost =
    process.env.NODE_ENV === "production" && process.env.VERCEL === "1"
      ? hostname
      .replace(`.domain.org`, "")
      .replace(`.###.vercel.app`, "")
      : hostname.replace(`.localhost:3000`, "");

  if(process.env.NODE_ENV !== "production") {
    if(pathname.startsWith(`/_templates`)) {
      url.pathname = pathname
      return NextResponse.rewrite(url);
    }
  }

  if (pathname.startsWith(`/_sites`) || pathname.startsWith(`/_templates`))
    return new Response(null, {
      status: 404,
    });


  if (!pathname.includes(".") && !pathname.startsWith("/api")) {
    if (currentHost === "app") {
      if (
        pathname === "/login" &&
        (req.cookies["next-auth.session-token"] ||
          req.cookies["__Secure-next-auth.session-token"])
      ) {
        url.pathname = "/";
        return NextResponse.redirect(url);
      }

      url.pathname = `/app${pathname}`;
      return NextResponse.rewrite(url);
    }

    if (
      hostname === "localhost:3000" ||
      hostname === "domain.org" ||
      hostname === "###.vercel.app"
    ) {
      url.pathname = `/home${url.pathname}`;
      return NextResponse.rewrite(url);
    }

    if(currentHost === "blog") {
      url.pathname = `/_sites/${currentHost}${url.pathname}`;
      return NextResponse.rewrite(url);
    }
    
    
    url.pathname = `/_templates/router`;
    return NextResponse.rewrite(url);
  }

Any idea?

Delete domain doesn't seem to work

This Delete Domain API URL doesn't seem to work on my Vercel PRO team account.
On line 7 if remove-domain.js

DELETE    https://api.vercel.com/v8/projects/${process.env.VERCEL_PROJECT_ID}/domains/${domain}?teamId=${process.env.VERCEL_TEAM_ID}`

I found that instead the following one works:

DELETE    https://api.vercel.com/v6/domains/${domain}

It doesn't return any error, it simply doesn't delete the domain from my Vercel account

Add error toasts on login page

What

A nice improvement to offer out of the box would be error prompts for the login page (User can see if it's an issue with something they did, or a server-side issue, etc). Seeing as react-hot-toast is already used in other places, we could use that combined with next/router to check for any error parameters returned by next-auth

Relative URL warning in middleware

This is currently the following warning when running the server

warn - using relative URLs for Middleware will be deprecated soon - https://nextjs.org/docs/messages/middleware-relative-urls

Cannot Deploy - Type error: This expression is not callable.

I checked Middleware #132 , I'm not sure how or why Type 'typeof import("/vercel/path0/node_modules/unist-util-visit/index")' has no call signatures.

I'm very stuck and confused.

Log

[23:06:36.129] Cloning github.com/kobykotiv/platforms (Branch: main, Commit: 7cd0f08)
[23:06:36.466] Cloning completed: 336.976ms
[23:06:37.341] Looking up build cache...
[23:06:44.510] Build cache downloaded [138.84 MB]: 6781.551ms
[23:06:44.817] Running "vercel build"
[23:06:45.467] Vercel CLI 27.3.2
[23:06:45.895] Warning: Detected "engines": { "node": ">=14.0.0" } in your `package.json` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version
[23:06:45.911] Installing dependencies...
[23:06:51.576] 
[23:06:51.577] added 62 packages, removed 143 packages, and changed 65 packages in 5s
[23:06:51.577] 
[23:06:51.577] 176 packages are looking for funding
[23:06:51.577]   run `npm fund` for details
[23:06:51.594] Detected Next.js version: 12.2.2
[23:06:51.600] Detected `package-lock.json` generated by npm 7+...
[23:06:51.600] Running "npm run build"
[23:06:51.980] 
[23:06:51.980] > prebuild
[23:06:51.980] > prisma generate
[23:06:51.982] 
[23:06:52.616] Prisma schema loaded from prisma/schema.prisma
[23:06:53.607] 
[23:06:53.607] ✔ Generated Prisma Client (3.8.1 | library) to ./node_modules/@prisma/client in 225ms
[23:06:53.607] You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client
[23:06:53.608] ```
[23:06:53.608] import { PrismaClient } from '@prisma/client'
[23:06:53.610] const prisma = new PrismaClient()
[23:06:53.611] ```
[23:06:53.720] 
[23:06:53.721] > build
[23:06:53.721] > next build
[23:06:53.721] 
[23:06:54.374] info  - Linting and checking validity of types...
[23:07:04.051] Failed to compile.
[23:07:04.051] 
[23:07:04.052] ./lib/remark-plugins.tsx:35:7
[23:07:04.052] Type error: This expression is not callable.
[23:07:04.052]   Type 'typeof import("/vercel/path0/node_modules/unist-util-visit/index")' has no call signatures.
[23:07:04.052] 
[23:07:04.052] �[0m �[90m 33 | �[39m      �[36mconst�[39m nodesToChange �[33m=�[39m �[36mnew�[39m �[33mArray�[39m�[33m<�[39m�[33mNodesToChange�[39m�[33m>�[39m()�[33m;�[39m�[0m
[23:07:04.053] �[0m �[90m 34 | �[39m�[0m
[23:07:04.053] �[0m�[31m�[1m>�[22m�[39m�[90m 35 | �[39m      visit(tree�[33m,�[39m �[32m"text"�[39m�[33m,�[39m (node�[33m:�[39m any) �[33m=>�[39m {�[0m
[23:07:04.053] �[0m �[90m    | �[39m      �[31m�[1m^�[22m�[39m�[0m
[23:07:04.053] �[0m �[90m 36 | �[39m        �[36mif�[39m (�[0m
[23:07:04.053] �[0m �[90m 37 | �[39m          node�[33m.�[39mvalue�[33m.�[39mmatch(�[0m
[23:07:04.053] �[0m �[90m 38 | �[39m            �[35m/https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)([^\?])(\?.*)?/g�[39m�[0m
[23:07:04.081] 
[23:07:04.081] > Build error occurred
[23:07:04.085] Error: Call retries were exceeded
[23:07:04.085]     at ChildProcessWorker.initialize (/vercel/path0/node_modules/next/dist/compiled/jest-worker/index.js:1:11661)
[23:07:04.085]     at ChildProcessWorker._onExit (/vercel/path0/node_modules/next/dist/compiled/jest-worker/index.js:1:12599)
[23:07:04.086]     at ChildProcess.emit (node:events:527:28)
[23:07:04.086]     at Process.ChildProcess._handle.onexit (node:internal/child_process:291:12) {
[23:07:04.086]   type: 'WorkerError'
[23:07:04.086] }
[23:07:04.201] Error: Command "npm run build" exited with 1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.