Всем привет! Пару месяцев назад у нас возникла задача запилить лендос для нашего онлайн сервиса. Наш стек - Create React App + .Net Core. Погугля немного, мы решили, что хотим запилить лендос на Next JS, но возник вопрос - как это все вместе подружить.
Мы хотели, что бы приложение открывалось по ссылке: yourdomain.com/app, а все остальные ссылки вели бы на лендос.


Для начала в папке, где у Вас лежит ClientApp нужно создать папку LandingApp куда вы добавите второй проект. (Если что, папки можно назвать, как угодно)


После того, как Вы добавите второй проект нужно немного обновить startup.cs, чтобы .Net Core мог "переключать трафик" с одного проекта на другой.

Отключаем endpoint routing в методе ConfigureServices

            services.AddMvc(options =>
            {
                options.EnableEndpointRouting = false;
            });

В метод Configure добавляем статичные файлы для CRA

            app.UseSpaStaticFiles(new StaticFileOptions() { 
                RequestPath = "/app"
            });

Добавляем маппер, который при переходе по ссылке /app будет открывать наше приложение

app.MapWhen(context => context.Request.Path.Value.StartsWith("/app"), builder =>
            {
                builder.UseMvc(routes =>
                {
                    routes.MapSpaFallbackRoute(
                        "app",
                        new { controller = "", action = "app" }
                    );
                });
                builder.UseSpa(spa =>
                {
                    spa.Options.SourcePath = "ClientApp";
              
                    if (env.IsDevelopment())
                    {
                        spa.UseReactDevelopmentServer(npmScript: "start");
                    }

                });
            });

После по дефолту открываем наше Next JS приложение

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "LandingApp";
                // Для простоты билда, мы забилдили лендос отдельно
                // И там скрывается ссылка по типу 
                // https://yourapp.azurewebsites.net
                var url = Configuration.GetSection("urls")["landingUrl"];
                if (env.IsDevelopment())
                {
                    url = "http://localhost:3005";
                }
                spa.UseProxyToSpaDevelopmentServer(url);
                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "dev");
                }
            });
Весь метод Configure
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            _env = 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();
            }

            app.Use(async (context, next) =>
            {
                context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
                await next();
            });

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseSpaStaticFiles(new StaticFileOptions() { 
                RequestPath = "/app"
            });

            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
            });

            app.MapWhen(context => context.Request.Path.Value.StartsWith("/app"), builder =>
            {
                builder.UseMvc(routes =>
                {
                    routes.MapSpaFallbackRoute(
                        "app",
                        new { controller = "", action = "app" }
                    );
                });
                builder.UseSpa(spa =>
                {
                    spa.Options.SourcePath = "ClientApp";
              
                    if (env.IsDevelopment())
                    {
                        spa.UseReactDevelopmentServer(npmScript: "start");
                    }

                });
            });

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "LandingApp";
                var url = Configuration.GetSection("urls")["landingUrl"];
                if (env.IsDevelopment())
                {
                    url = "http://localhost:3005";
                }
                spa.UseProxyToSpaDevelopmentServer(url);
                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "dev");
                }
            });

        }

Далее, нам нужно сказать CRA откуда брать бандл т.к. теперь он находится не в корне, а в /app. Для этого, мы в package.json указываем свойство homepage: "/app/"

Если вы используете библиотеку history то нужно указать еще basename: '/app'

import { connectRouter } from 'connected-react-router';
import { createBrowserHistory } from 'history';

export const history = createBrowserHistory({
  basename: '/app',
});

export const routerReducer = connectRouter(history);

И на этом вроде все :)

Комментарии (8)


  1. Sm1le291
    16.10.2021 19:09
    +4

    Не совсем понятно что вы пытались сделать. Понял только что какой то лендос. Обычно в компаниях разводят js и. Net core, потому что над ними работают работают разные команды в разных редакторах, да и деплоят их отдельно.

    Вы бы лучше написали как их развести, а развести их очень просто, нужно создать в iis два разных веб сайта и деплоить на них отдельно фронт и бэк, при этом они могут быть на одном урле с разными портами.

    А сведенные они и так по дефолту, когда вы создаете шаблонное приложение в VS, React или Angular


    1. Antek-ESD Автор
      16.10.2021 19:13

      Мы не только пытались, но и сделали) Представьте у Вас есть домен: https://yourdomain.com.
      Вы хотите что бы когда пользователь заходит на https://yourdomain.com открывалась страница сделанная на Next JS (LandingApp). Но при переходе на https://yourdomain.com/app открывалось приложение на Create React App (ClientApp). Надеюсь так понятнее будет :)


      1. K1aidy
        16.10.2021 20:56

        В последнее время модно называть это микрофронтендами. Ваш метод приведет к минусам монолитов, если приложение разрастется.


        1. Antek-ESD Автор
          16.10.2021 21:05

          Тут все зависит от дальнейшей организации и от задачи. У нас, на пример, несколько бэкендов которые отвечают за разные вещи.


    1. navferty
      16.10.2021 22:03

      К слову, в IIS можно и на одном порту два приложения развернуть (например, чтобы избежать проблему с CORS'ами). Правой кнопкой мыши по сайту -> "новое приложение", тогда оно будет доступно на том же хосте, но по соответствующему имени приложения пути: например, localhost:7777/appName


      1. xTuMoHx
        16.10.2021 22:51
        +4

        Почему такая привязка к IIS, dotnet давно кроссплатформенный, я бы лично через nginx разбирал урл по алиасам


        1. Antek-ESD Автор
          16.10.2021 22:53

          Не знаю, если билдить через azure app service, на пример, то там ни к портам ни к nginx доступа нет, так что выбор решения зависит от хостинга и конфигурации сервера.


          1. IVANS0N
            17.10.2021 05:22
            +2

            Это потому что у Azure свой gateway с балансировщиком есть, в котором можно настроить все эти правила для апсервисов