A Flutter Web Plugin to display Text Widget as Html for SEO purpose

Overview

SEO Renderer

Pub

A flutter plugin (under development) to render text widgets as html elements for SEO purpose.

Created specifically for issue https://github.com/flutter/flutter/issues/46789 It will automatic detect the crawler using regex and navigator userAgent and add the HtmlElement you choose to the DOM.

All PR's are welcome :)

Getting Started

  • Add this to your pubspec.yaml

    dependencies:
      seo_renderer: ^0.3.0
  • Get the package from Pub:

    flutter packages get
  • Import it in your file

    import 'package:seo_renderer/seo_renderer.dart';

Usage

First we need to add a RouteObserver to automatically remove Html Elements when popped from the Navigation Stack. To do this simply add this line in MaterialApp

navigatorObservers: <RouteObserver<ModalRoute<void>>>[ routeObserver ],

ps : routeObserver is an object, which can be found in utils.dart file.

There are 3 Widgets, TextRenderer, LinkRenderer & ImageRenderer

TextRenderer

TextRenderer Just pass the element new ParagraphElement(), new HeadingElement() or one of other HtmlElement and your Text/RichText Widget and an optional RenderController() which can be used to refresh the content(position) in case of Scrollable Content/ Changed Position .

Paragraph

TextRenderer(
  element: new ParagraphElement(), // This is ParagraphElement by default
  text: Text(
      'Paragraph: Lorem Ipsum is simply dummy text of the printing and typesetting industry.'),
),

Heading

TextRenderer(
  element: new HeadingElement.h1(),
  text: Text(
      'Heading H1: Lorem Ipsum is simply dummy text of the printing and typesetting industry.'),
),

LinkRenderer

Need to pass child : Widget, anchorText : String, link : String & an optional RenderConroller()

Example :

LinkRenderer(
  anchorText: 'Try Flutter',
  link: 'https://www.flutter.dev',
  child: OutlinedButton(
    onPressed: () {
      launch('https://www.flutter.dev');
    },
    child: Text('Flutter.dev'),
  ),
),

ImageRenderer

Need to pass child : Widget, link : String, alt : String & an optional RenderConroller()

Example :

ImageRenderer(
  alt: 'Flutter logo',
  link:
      'https://flutter.dev/assets/images/shared/brand/flutter/logo/flutter-lockup.png',
  child: Image.network(
      "https://flutter.dev/assets/images/shared/brand/flutter/logo/flutter-lockup.png"
  ),
),

RenderController have 2 methods

  • refresh() : Useful in case Widget changes position and you want it too.
  • clear() : Sometimes on Push operation Html Elements can't be removed. Please use this in your Push Operation.

ScreenShot & Example

Live example https://seo-renderer.netlify.app/

Select GoogleBot here's how as userAgent in Network Condition and refresh the page to see created Div Elements.

License

MIT License

Copyright (c) 2021 Sahdeep Singh

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Author & support

This project is created by Sahdeep Singh

If you appreciate my work you can connect and endorse me on LinkedIn to keep me motivated :)

Comments
  • Would you like to make this a Flutter Bounty Hunters project?

    Would you like to make this a Flutter Bounty Hunters project?

    Hi, I came across this package and it seems like an interesting direction for search indexing of Flutter webpages.

    At the Flutter Bounty Hunters, we've been working on document editors and renderers in Super Editor. We also build our webpages with Flutter for web. This project looks like it would make sense within that ecosystem.

    If you'd like to chat about migrating this project into our portfolio, and then working on getting funding for it, please let me know. You can respond here, or send me a DM on twitter, or message me at https://flutterbountyhunters.com

    opened by matthew-carroll 4
  • Use isbot package to detect robot

    Use isbot package to detect robot

    Potentially it could be better to use something like this https://www.npmjs.com/package/isbot to detect the robot:

    <script>
      if (isbot(navigator.userAgent)) {
        window.flutterWebRenderer = "html";
      }
    </script>
    

    And pass simple boolean down to RobotDetector dart code, just that I'm not that familiar with NPM and how to use it within html.

    enhancement 
    opened by krokyze 4
  • Can not detect GoogleBot userAgent in Network Condition and refresh the page to see created Div Elements.

    Can not detect GoogleBot userAgent in Network Condition and refresh the page to see created Div Elements.

    I'm wrapping my MaterialApp with RobotDetector and Text widgets with TextRenderer. But when I Inspect Elements with GoogleBot userAgent, there is no extra html div's created by [seo_renderer] which google's bot gonna detect.

    Which is mentioned in your app .

    opened by esc-0-bar 2
  • Range Error (index) when using TextRenderer inside a List

    Range Error (index) when using TextRenderer inside a List

    Hello, I'm trying to fetch an list of names and it keeps getting error of index when using ListView or Column to map a List, here's the example page

    import 'package:flutter/material.dart';
    import 'package:flutter_modular/flutter_modular.dart';
    import 'package:flutter_web_poc/presentation/controllers/seo/seo_controller.dart';
    import 'package:seo_renderer/seo_renderer.dart';
    
    class SeoPage extends StatefulWidget {
      const SeoPage({Key? key}) : super(key: key);
    
      @override
      State<SeoPage> createState() => _SeoPageState();
    }
    
    class _SeoPageState extends State<SeoPage> {
      final controller = Modular.get<SeoController>();
    
      @override
      void initState() {
        super.initState();
        controller.fetchAllTodos();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const TextRenderer(
              child: Text('Seo Page'),
            ),
          ),
          body: AnimatedBuilder(
            animation: controller,
            builder: (context, widget) {
              return ListView.builder(
                itemCount: controller.todos.length,
                itemBuilder: (context, index) {
                  final todo = controller.todos[index];
                  return ListTile(
                    title: TextRenderer(
                      style: TextRendererStyle.header1,
                      child: Text(
                        todo.title,
                      ),
                    ),
                  );
                },
              );
            },
          ),
        );
      }
    }
    

    Here's my RobotDetector widget, it seems to me that nothing is wrong with it

    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        Modular.setObservers(
            [seoRouteObserver]); // replacement for navigatorObservers
        return RobotDetector(
          debug: true,
          child: MaterialApp.router(
            title: 'Flutter Web Poc',
            routeInformationParser: Modular.routeInformationParser,
            routerDelegate: Modular.routerDelegate,
            debugShowCheckedModeBanner: false,
          ),
        );
      }
    }
    

    Error Message:

    The following IndexError was thrown during a scheduler callback:
    RangeError (index): Index out of range: index must not be negative: -12
    
    When the exception was thrown, this was the stack
    dart-sdk[/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart]() 251:49  throw_
    dart-sdk[/lib/_internal/js_dev_runtime/private/js_array.dart]() 581:7 _get]
    lib[/_engine/engine/canvaskit/embedded_views.dart]() 647:52 [_updateOverlays] 
    packages[/flutter/src/rendering/binding.dart]() 501:18                            drawFrame
    packages[/flutter/src/widgets/binding.dart]() 883:13                              drawFrame
    packages[/flutter/src/rendering/binding.dart]() 363:5                             [_handlePersistentFrameCallback]
    packages[/flutter/src/scheduler/binding.dart]() 1144:15                           [_invokeFrameCallback]
    packages[/flutter/src/scheduler/binding.dart]() 1081:9                            handleDrawFrame
    packages[/flutter/src/scheduler/binding.dart]() 995:5                             [_handleDrawFrame]
    lib[/_engine/engine/platform_dispatcher.dart]() 1011:13                           invoke
    lib[/_engine/engine/platform_dispatcher.dart]() 159:5                             invokeOnDrawFrame
    lib[/_engine/engine/initialization.dart]() 128:45                                 <fn>
    ════════════════════════════════════════════════════════════════════════════════
    

    I'm using the Modular package for dependency injection by the way

    • Flutter version: 2.10.2
    • Seo_Renderer lib version: 0.0.5
    opened by felipesses 2
  • TextRenderer Widget throws

    TextRenderer Widget throws "Unexpected null value." at using inside a Row or Column

    I'm trying to use TextRenderer inside to any Rows and Columns and always throws me error. But, the showed error don't say me what the problem, because, it's throwed a default error. Any observations:

    • Flutter version: 2.10
    • Seo_Renderer lib version: 0.0.5

    Example code:

    Row(
      mainAxisAlignment: MainAxisAlignment.start,
      children: [
      InkWell(
        onTap: () {
          goToHomePage();
        },
        child: Container(
          width: 500,
          height: 200,
          child: TextRenderer(
            text: "Choose more products",
            style: SeoUtils.getTextRendererStyleByFontSize(14),
            child: Text(
              "Choose more products",
              style: TextStyle(
                fontSize: 14,
                color: const Color(0xFF3B3B3B),
                decoration: TextDecoration.underline,
                decorationColor: const Color(0xFF3B3B3B),
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),
      ),
      Container(child: Icon(Icons.add),),
      ],
      );
    

    Follow below stack errors:

    ════════ Exception caught by widgets library ═══════════════════════════════════ The following TypeErrorImpl was thrown building TextRenderer(dirty, dependencies: [_ModalScopeStatus], state: _TextRendererState#a019f): Unexpected null value.

    The relevant error-causing widget was TextRenderer When the exception was thrown, this was the stack C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/internal/js_dev_runtime/private/ddc_runtime/errors.dart 251:49 throw C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 528:63 nullCheck packages/seo_renderer/helpers/robot_detector_web.dart 20:68 detected packages/seo_renderer/renderers/text_renderer/text_renderer_web.dart 94:24 build packages/flutter/src/widgets/framework.dart 4870:27 build packages/flutter/src/widgets/framework.dart 4754:15 performRebuild packages/flutter/src/widgets/framework.dart 4928:11 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4919:11 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 6284:14 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 6284:14 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4928:11 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4919:11 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 6284:14 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 6284:14 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4928:11 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4919:11 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 6284:14 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4928:11 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4919:11 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4928:11 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4919:11 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4928:11 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4919:11 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 6422:36 inflateWidget packages/flutter/src/widgets/framework.dart 6433:32 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4928:11 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4919:11 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 6422:36 inflateWidget packages/flutter/src/widgets/framework.dart 6433:32 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 6284:14 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 6284:14 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 6284:14 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 6284:14 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 6284:14 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4928:11 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4919:11 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/framework.dart 4780:16 performRebuild packages/flutter/src/widgets/framework.dart 4477:5 rebuild packages/flutter/src/widgets/framework.dart 4735:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 4729:5 mount packages/flutter/src/widgets/framework.dart 3790:13 inflateWidget packages/flutter/src/widgets/framework.dart 3540:18 updateChild packages/flutter/src/widgets/sliver.dart 1243:37 updateChild packages/flutter/src/widgets/sliver.dart 1228:20 packages/flutter/src/widgets/framework.dart 2600:19 buildScope packages/flutter/src/widgets/sliver.dart 1221:5 createChild packages/flutter/src/rendering/sliver_multi_box_adaptor.dart 349:23 packages/flutter/src/rendering/object.dart 1997:59 packages/flutter/src/rendering/object.dart 918:15 [_enableMutationsToDirtySubtrees] packages/flutter/src/rendering/object.dart 1997:7 invokeLayoutCallback packages/flutter/src/rendering/sliver_multi_box_adaptor.dart 338:5 [_createOrObtainChild] packages/flutter/src/rendering/sliver_multi_box_adaptor.dart 484:5 insertAndLayoutChild packages/flutter/src/rendering/sliver_list.dart 239:19 advance packages/flutter/src/rendering/sliver_list.dart 281:12 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/viewport.dart 510:12 layoutChildSequence packages/flutter/src/rendering/viewport.dart 1580:12 [_attemptLayout] packages/flutter/src/rendering/viewport.dart 1489:20 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/custom_paint.dart 545:11 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/custom_layout.dart 171:10 layoutChild packages/flutter/src/material/scaffold.dart 1005:7 performLayout packages/flutter/src/rendering/custom_layout.dart 240:7 [_callPerformLayout] packages/flutter/src/rendering/custom_layout.dart 403:14 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/proxy_box.dart 1376:11 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/proxy_box.dart 3430:13 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/widgets/overlay.dart 751:14 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/widgets/layout_builder.dart 321:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/custom_layout.dart 171:10 layoutChild packages/flutter/src/material/scaffold.dart 1005:7 performLayout packages/flutter/src/rendering/custom_layout.dart 240:7 [_callPerformLayout] packages/flutter/src/rendering/custom_layout.dart 403:14 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/proxy_box.dart 1376:11 performLayout packages/flutter/src/rendering/object.dart 1887:7 layout packages/flutter/src/rendering/proxy_box.dart 116:7 performLayout packages/flutter/src/rendering/object.dart 1731:7 [_layoutWithoutResize] packages/flutter/src/rendering/object.dart 887:17 flushLayout packages/flutter/src/rendering/binding.dart 497:19 drawFrame packages/flutter/src/widgets/binding.dart 883:13 drawFrame packages/flutter/src/rendering/binding.dart 363:5 [_handlePersistentFrameCallback] packages/flutter/src/scheduler/binding.dart 1144:15 [_invokeFrameCallback] packages/flutter/src/scheduler/binding.dart 1081:9 handleDrawFrame packages/flutter/src/scheduler/binding.dart 995:5 [_handleDrawFrame] C:/b/s/w/ir/cache/builder/src/out/host_debug/flutter_web_sdk/lib/_engine/engine/platform_dispatcher.dart 1011:13 invoke C:/b/s/w/ir/cache/builder/src/out/host_debug/flutter_web_sdk/lib/_engine/engine/platform_dispatcher.dart 159:5 invokeOnDrawFrame C:/b/s/w/ir/cache/builder/src/out/host_debug/flutter_web_sdk/lib/_engine/engine/initialization.dart 128:45 ════════════════════════════════════════════════════════════════════════════════

    opened by weth767 2
  • RendererScrollListener widget missing

    RendererScrollListener widget missing

    Hi! First of all thank you for creating this amazing package and could the only solution available for flutter web SEO issue.

    I would like to ask on how to implement the RendererScrollListener using the package as it is not readily available upon importing the package.

    I did try to create my own by listening to the ScrollController and refreshing on ScrollEnd however this is not a very performant solution. Is it possible to give suggestion if in case the RendererScrollListener is not available? thank you.

    opened by rirjkl19 2
  • Implement ScrollAware within *Renderer widgets

    Implement ScrollAware within *Renderer widgets

    This pull request implements RendererScrollListener just so all renderer widgets can easily subscribe to scroll updates and no extra controllers need to be passed down the widget tree.

    opened by krokyze 2
  • Refactor all widgets to HtmlElementView

    Refactor all widgets to HtmlElementView

    This pull request:

    • Migrates all widgets to HtmlElementView so now scrolling, updating absolute positions, adding, removing html elements is automatically handled and there's no need of RenderScrollListener or RouteObserver.
    • Adds TextRendererStyle to fix #10
    • Moves _regExp.hasMatch to root RobotDetector so it isn't called every single time *Renderer widget builds
    • Adds debug option so robot mode can be easily enabled
    • Adds optional text for TextRenderer
    • Changes src to optional for ImageRenderer
    opened by krokyze 1
  • support specifying heading elements without importing dart:html

    support specifying heading elements without importing dart:html

    It would be nice if Header elements could be specified for TextRenderer without having to import dart:html. This would allow making sure certain things are rendered as Headers when needed without making the code dependent on web.

    One approach for doing this would be to replace the element parameter on TextRenderer with an Enum like this:

    enum
    enum HtmlTextType {
      paragraph,
      header1,
      header2,
      header3,
      header4,
      header5,
      header6,
    }
    

    Both the web text renderer and the vm text renderer could accept this parameter and handle it differently: by converting it into a Html Heading element on web and ignoring it on VM.

    type conversion
      HtmlElement typeToElement(HtmlTextType? type) {
        switch (type) {
          case HtmlTextType.header1:
            return HeadingElement.h1();
          case HtmlTextType.header2:
            return HeadingElement.h2();
          case HtmlTextType.header3:
            return HeadingElement.h3();
          case HtmlTextType.header4:
            return HeadingElement.h4();
          case HtmlTextType.header5:
            return HeadingElement.h5();
          case HtmlTextType.header6:
            return HeadingElement.h6();
          case HtmlTextType.paragraph:
          default:
            return ParagraphElement();
        }
      }
    
    opened by clragon 1
  • Why doesn't there exists not html tag but only canvas tag?

    Why doesn't there exists not html tag but only canvas tag?

    Hi iamSahdeep. I tried running your sample in your library, but couldn't find the difference in chrome developer's tool. I want to know if it is working for now. There is no div tag when I run this project.

    opened by changgyu-brandi 1
  • Feature/issues/5 add other elements than div

    Feature/issues/5 add other elements than div

    Hi @iamSahdeep,

    This is a MR about that ticket: https://github.com/iamSahdeep/seo_renderer/issues/5 Give your feedback or maybe do some ajustement.

    I want to know if it feels right to you.

    Thx

    opened by t1gu1 1
  • No longer need of ? operation for SchedulerBinding.instance

    No longer need of ? operation for SchedulerBinding.instance

    This is a very old problem which was not fixed in this package. Since flutter 3.0.0 just made this stable and the latest stable flutter version is 3.3.0 , this issue should be fixed a long time ago.

    opened by esc-0-bar 0
  • migrate flutter version to 3

    migrate flutter version to 3

    I created this pull request to prevent warnings in Flutter 3. Here is what I did. What do you think about this?

    • minimum flutter sdk version to 3.0.0
    • remove null check operator from SchedulerBinding
    • update pubs
    • add lint directive
    opened by cy-yusuke-abe 2
  • Another approach that I use in case your curious

    Another approach that I use in case your curious

    I used a different solution , which I used for a similar problem.

    The content of a flutter web app is not in the flutter web app. It’s coming from the database and static files on the server.

    This includes both the static text and images like you would have in your flutter code, and the dynamic text and images that comes from the server anyways.

    In the flutter web app it asks for its content from the server and also caches the static content , so it does not have to ask for it again. So it’s really just doing templating client side but not exactly.

    when a google ( or other search engine ) not requests a page , it gets a different template on the server and mixes it together with the content and returns it.

    This works very well and is a clean solution. But it requires more work I admit.

    The mobile flutter app is the same as the web flutter code. It also asks for the static content from the server and then caches it . And for dynamic data is also asks the server for the data.

    Often you need to do things like this anyway for apps that need to be in many languages anyway. Called i18n.

    If you have used the flutter translation tools you will see that it extracts all static text out of flutter code into an arb and json file. So you just need to put that file on the server. Then when the flutter code runs on the client , it pulls the json file up from the server and binds the text into the flutter code and it all renders.

    inages use the exact same trick. You just need to build a flutter AST parser that returns back the images in the same way the text translation does.

    The advantage is that your flutter binaries will be quite a bit smaller. Especially if you have lots of static text and images in your flutter code.

    i actually stopped using flutter, but not because of the way flutter web works with SEO. I just found flutter to be very immature compared to other systems in rust and golang which do exactly what flutter does.

    with golang and rust approaches you have a better language to do complex things . I know that flutter is great but I already knew golang and rust and so using gioui ( golang ) and yew ( rust ) is more efficient for me and other work colleagues.

    Anyways I hope the pattern I described above helps other developers using flutter and wishing to get high quality SEO.

    you can also output json-ld data to make the google search system even happier.

    opened by gedw99 1
  • How to integrate go_router with seo_renderer?

    How to integrate go_router with seo_renderer?

    Hello, I am using go_router for my project. Recently I wanted to integrate seo_renderer into my project.

    https://pub.dev/packages/go_router

    https://pub.dev/packages/seo_renderer

    However, the examples show that these 2 libraries are not compatible with each other.

    // seo_renderer:
    runApp(
      RobotDetector(
        debug: true, // you can set true to enable robot mode
        child: MaterialApp(
          home: MyApp(),
          navigatorObservers: [seoRouteObserver],
        ),
      ),
    );
    
    // go_router
    final _router = GoRouter(
      observers: [seoRouteObserver], // Not working.
      routes: [ ...]
    );
    // navigatorObservers is setted to null.
    MaterialApp.router(
       routeInformationParser: _router.routeInformationParser,
       routerDelegate: _router.routerDelegate,
      title: 'GoRouter Example',
    );
    

    This my my question in stackoverflow:

    https://stackoverflow.com/questions/72500928/how-to-integrate-go-router-with-seo-renderer

    Hope to get an example from you.

    opened by o7planning 2
  • Using seo_renderer with Navigator 2.0

    Using seo_renderer with Navigator 2.0

    Hello,

    I'm very interesting by your lib, but i cannot implement it if I use Navigator 2.0.

    In my main, i'm using MaterialApp.router(, and there is no observers or navigatorObserver key.

    Do you have a solution for that ?

    Many thanks

    opened by gabmagnan 5
Releases(v0.5.0)
  • v0.5.0(Feb 23, 2022)

    0.5.0

    • Using Widget RobotDetector to detect google bot etc. debug option to enable robot mode even if user agent don't have it.
    • routeObserver name changed to seoRouteObserver
    • TextRenderer now can have Text or RichText as child or simply text as String. if both supplied text property will be prioritised.
    • style property now have TextRendererStyle which is Enum, defaults to TextRendererStyle.paragraph
    • LinkRenderer have text property instead of anchorText and href instead of link to provide Link.
    • ImageRenderer can have src which is String type and child which can be any ImageProvider. src will have priority in both them.
    • RenderScrollListener is removed and scrolls will be automatically handled.
    • All thanks to @krokyze, see PR11
    Source code(tar.gz)
    Source code(zip)
  • v0.4.0(Feb 9, 2022)

    New ScrollAware widget RendererScrollListener to listen and update renderer widgets without RenderController. see PR #9 Note : This version removes the need of RenderController

    Source code(tar.gz)
    Source code(zip)
  • v0.3.0(Oct 18, 2021)

  • v0.2.0(Sep 9, 2021)

  • v0.1.0(Jul 5, 2021)

  • v0.0.2(Jun 28, 2021)

    Include TextRenderer and LinkRenderer Element with will be same as Widget width Automatically removing the Element in case of Pop Action.

    Source code(tar.gz)
    Source code(zip)
Owner
Sahdeep Singh
Sahdeep Singh
This plugin allows Flutter desktop apps to extract text from screen.

screen_text_extractor This plugin allows Flutter desktop apps to extract text from screen. screen_text_extractor Platform Support Quick Start Installa

LeanFlutter 30 Dec 21, 2022
Dart web - Experimental web framework for Dart. Supports SPA and SSR

dart_web Experimental web framework for Dart. Supports SPA and SSR. Relies on pa

Kilian Schulte 307 Dec 23, 2022
Binder is a web framework that can be used to create web apps and APIs .

Binder Framework Binder is a web framework that can be used to create web apps and APIs . It's like React + Express or any combination of front-end fr

Kab Agouda 8 Sep 13, 2022
File picker plugin for Flutter, compatible with mobile (iOS & Android), Web, Desktop (Mac, Linux, Windows) platforms with Flutter Go support.

A package that allows you to use the native file explorer to pick single or multiple files, with extensions filtering support.

Miguel Ruivo 987 Jan 6, 2023
Fluttern is a web app made with Flutter to list Flutter internships/jobs for the community.

Fluttern Fluttern is a web app made with Flutter to list Flutter internships/jobs for the community. It uses Google Sheet as a backend, simplifying th

Aditya Thakur 3 Jan 5, 2022
A platform adaptive Flutter app for desktop, mobile and web.

Flutter Folio A demo app showcasing how Flutter can deliver a great multi-platform experience, targeting iOS, Android, MacOS, Windows, Linux, and web.

gskinner team 3.5k Jan 2, 2023
A Portfolio Website - Flutter Web

A Portfolio Website - Flutter Web Watch it on YouTube This UI is not Responsive A nice clean Portfolio Website for Designer or developers. Which inclu

Abu Anwar 355 Dec 31, 2022
Pure Dart Argon2 algorithm (the winner of the Password Hash Competition 2015) for all Dart platforms (JS/Web, Flutter, VM/Native).

argon2 Pure Dart Argon2 algorithm (the winner of the Password Hash Competition 2015) for all Dart platforms (JS/Web, Flutter, VM/Native). Based on the

Graciliano Monteiro Passos 8 Dec 22, 2021
Serverpod is a next-generation app and web server, explicitly built for the Flutter and Dart ecosystem.

Serverpod Serverpod is a next-generation app and web server, explicitly built for the Flutter and Dart ecosystem. It allows you to write your server-s

Serverpod 1k Jan 8, 2023
An eventual FIBS client written in Flutter and hosted on the web

fibscli An eventual FIBS client written in Flutter and hosted on the web. status Currently, the app works as a stand-alone backgammon game w/o connect

Chris Sells 10 Oct 26, 2022
A project that makes use of a Typescript back end and a Flutter web front end to consume the Jira API in order to visualize and interact with issues.

A project that makes use of a Typescript back end and a Flutter web front end to consume the Jira API in order to visualize and interact with issues.

Lucas Coelho 1 Mar 20, 2022
Dawn - a Dart web package for developing UIs in a pattern similar to Flutter.

dawn Description Dawn is a Dart web package for developing UIs in a pattern similar to Flutter. Links GitHub Repository Pub Page Documentation An Exam

Hamed Aarab 45 Jan 6, 2023
A cross-platform app ecosystem, bringing iMessage to Android, PC (Windows, Linux, & even macOS), and Web!

BlueBubbles Android App BlueBubbles is an open-source and cross-platform ecosystem of apps aimed to bring iMessage to Android, Windows, Linux, and mor

BlueBubbles 318 Jan 8, 2023
A web dashboard that allows you to monitor your Chia farm and sends notifications when blocks are found and new plots are completed through a discord bot. It can link multiple farmers/harvesters to your account.

farmr A web dashboard that allows you to monitor your Chia farm and sends notifications when blocks are found and new plots are completed through a di

Gil Nobrega 261 Nov 10, 2022
Experimental web framework for Dart. Supports SPAs and SSR.

jaspr Experimental web framework for Dart. Supports SPAs and SSR. Main Features: Familiar component model similar to Flutter widgets Easy Server Side

Kilian Schulte 310 Jan 4, 2023
Flutter plugin for Flutter desktop(macOS/Linux/Windows) to change window size.

desktop_window Flutter plugin for Flutter desktop(macOS/Linux/Windows) to change window size. Usage import 'package:desktop_window/desktop_window.dart

ChunKoo Park 72 Dec 2, 2022
Flutter plugin to store data behind biometric authentication (ie. fingerprint)

biometric_storage Encrypted file store, optionally secured by biometric lock for Android, iOS, MacOS and partial support for Linux, Windows and Web. M

AuthPass 125 Dec 28, 2022
A Flutter plugin to read 🔖 metadata of 🎵 media files. Supports Windows, Linux & Android.

flutter_media_metadata A Flutter plugin to read metadata of media files. A part of Harmonoid open source project ?? Install Add in your pubspec.yaml.

Harmonoid 60 Dec 2, 2022
This plugin allows Flutter desktop apps to defines system/inapp wide hotkey (i.e. shortcut).

hotkey_manager This plugin allows Flutter desktop apps to defines system/inapp wide hotkey (i.e. shortcut). hotkey_manager Platform Support Quick Star

LeanFlutter 81 Dec 21, 2022